|
import Position from './position'; |
|
|
|
|
|
|
|
|
|
|
|
export default class Overlay { |
|
|
|
|
|
|
|
|
|
|
|
constructor({ |
|
opacity = 0.75, |
|
padding = 10, |
|
animate = true, |
|
}) { |
|
this.opacity = opacity; |
|
this.padding = padding; |
|
this.animate = animate; |
|
|
|
this.overlayAlpha = 0; |
|
this.positionToHighlight = new Position({}); |
|
this.highlightedPosition = new Position({}); |
|
this.redrawAnimation = null; |
|
this.highlightedElement = null; |
|
|
|
this.draw = this.draw.bind(this); |
|
|
|
this.window = window; |
|
this.document = document; |
|
|
|
this.prepareContext(); |
|
this.setSize(); |
|
} |
|
|
|
|
|
|
|
|
|
prepareContext() { |
|
const overlay = this.document.createElement('canvas'); |
|
|
|
this.overlay = overlay; |
|
this.context = overlay.getContext('2d'); |
|
|
|
this.overlay.style.pointerEvents = 'none'; |
|
this.overlay.style.background = 'transparent'; |
|
this.overlay.style.position = 'fixed'; |
|
this.overlay.style.top = '0'; |
|
this.overlay.style.left = '0'; |
|
this.overlay.style.zIndex = '999999999'; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
highlight(element, animate = true) { |
|
if (!element) { |
|
return; |
|
} |
|
|
|
|
|
element.onHighlightStarted(); |
|
|
|
|
|
if (this.highlightedElement) { |
|
this.highlightedElement.onDeselected(); |
|
} |
|
|
|
|
|
const position = element.getCalculatedPosition(); |
|
if (!position.canHighlight()) { |
|
return; |
|
} |
|
|
|
this.highlightedElement = element; |
|
this.positionToHighlight = position; |
|
|
|
|
|
|
|
if (!this.animate || !animate) { |
|
this.highlightedPosition = this.positionToHighlight; |
|
} |
|
|
|
this.draw(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
getHighlightedElement() { |
|
return this.highlightedElement; |
|
} |
|
|
|
|
|
|
|
|
|
clear() { |
|
this.positionToHighlight = new Position(); |
|
this.highlightedElement.onDeselected(); |
|
this.highlightedElement = null; |
|
|
|
this.draw(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
draw() { |
|
|
|
const canHighlight = this.positionToHighlight.canHighlight(); |
|
|
|
|
|
|
|
this.removeCloak(); |
|
|
|
this.addCloak(); |
|
|
|
const isFadingIn = this.overlayAlpha < 0.1; |
|
|
|
if (canHighlight) { |
|
if (isFadingIn) { |
|
|
|
this.highlightedPosition = this.positionToHighlight; |
|
} else { |
|
|
|
this.highlightedPosition.left += (this.positionToHighlight.left - this.highlightedPosition.left) * 0.18; |
|
this.highlightedPosition.top += (this.positionToHighlight.top - this.highlightedPosition.top) * 0.18; |
|
this.highlightedPosition.right += (this.positionToHighlight.right - this.highlightedPosition.right) * 0.18; |
|
this.highlightedPosition.bottom += (this.positionToHighlight.bottom - this.highlightedPosition.bottom) * 0.18; |
|
} |
|
} |
|
|
|
|
|
this.removeCloak({ |
|
posX: this.highlightedPosition.left - window.scrollX - this.padding, |
|
posY: this.highlightedPosition.top - window.scrollY - this.padding, |
|
width: (this.highlightedPosition.right - this.highlightedPosition.left) + (this.padding * 2), |
|
height: (this.highlightedPosition.bottom - this.highlightedPosition.top) + (this.padding * 2), |
|
}); |
|
|
|
|
|
if (canHighlight) { |
|
if (!this.animate) { |
|
this.overlayAlpha = this.opacity; |
|
} else { |
|
this.overlayAlpha += (this.opacity - this.overlayAlpha) * 0.08; |
|
} |
|
} else { |
|
|
|
this.overlayAlpha = Math.max((this.overlayAlpha * 0.85) - 0.02, 0); |
|
} |
|
|
|
|
|
|
|
this.window.cancelAnimationFrame(this.redrawAnimation); |
|
|
|
|
|
if (canHighlight || this.overlayAlpha > 0) { |
|
|
|
if (!this.overlay.parentNode) { |
|
document.body.appendChild(this.overlay); |
|
} |
|
|
|
|
|
|
|
if (!this.hasPositionHighlighted()) { |
|
this.redrawAnimation = this.window.requestAnimationFrame(this.draw); |
|
} else if (!this.animate && isFadingIn) { |
|
this.redrawAnimation = this.window.requestAnimationFrame(this.draw); |
|
} else { |
|
|
|
this.highlightedElement.onHighlighted(); |
|
} |
|
} else if (this.overlay.parentNode) { |
|
|
|
this.document.body.removeChild(this.overlay); |
|
} |
|
} |
|
|
|
hasPositionHighlighted() { |
|
return this.positionToHighlight.equals(this.highlightedPosition) && |
|
this.overlayAlpha > (this.opacity - 0.05); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
removeCloak({ |
|
posX = 0, |
|
posY = 0, |
|
width = this.overlay.width, |
|
height = this.overlay.height, |
|
} = {}) { |
|
this.context.clearRect(posX, posY, width, height); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
addCloak({ |
|
posX = 0, |
|
posY = 0, |
|
width = this.overlay.width, |
|
height = this.overlay.height, |
|
} = {}) { |
|
this.context.fillStyle = `rgba( 0, 0, 0, ${this.overlayAlpha} )`; |
|
this.context.fillRect(posX, posY, width, height); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
setSize(width = null, height = null) { |
|
|
|
|
|
this.overlay.width = width || this.window.innerWidth; |
|
this.overlay.height = height || this.window.innerHeight; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
refresh(animate = true) { |
|
this.setSize(); |
|
|
|
|
|
|
|
|
|
if (this.highlightedElement) { |
|
this.window.cancelAnimationFrame(this.redrawAnimation); |
|
this.highlight(this.highlightedElement, animate); |
|
this.highlightedElement.onHighlighted(); |
|
} |
|
} |
|
} |
|
|