|
import Position from './position'; |
|
import { ID_OVERLAY, OVERLAY_ZINDEX } from '../common/constants'; |
|
|
|
|
|
|
|
|
|
|
|
export default class Overlay { |
|
|
|
|
|
|
|
|
|
|
|
constructor(options, window, document) { |
|
this.options = options; |
|
|
|
this.overlayAlpha = 0; |
|
this.positionToHighlight = new Position({}); |
|
this.highlightedPosition = new Position({}); |
|
this.redrawAnimation = null; |
|
this.highlightedElement = null; |
|
this.lastHighlightedElement = null; |
|
|
|
this.draw = this.draw.bind(this); |
|
|
|
this.window = window; |
|
this.document = document; |
|
|
|
this.resetOverlay(); |
|
this.setSize(); |
|
} |
|
|
|
|
|
|
|
|
|
resetOverlay() { |
|
|
|
const canvasOverlay = this.document.getElementById(ID_OVERLAY); |
|
if (canvasOverlay && canvasOverlay.parentNode) { |
|
canvasOverlay.parentNode.removeChild(canvasOverlay); |
|
} |
|
|
|
const overlay = this.document.createElement('canvas'); |
|
|
|
this.overlay = overlay; |
|
this.context = overlay.getContext('2d'); |
|
|
|
this.overlay.id = ID_OVERLAY; |
|
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 = OVERLAY_ZINDEX; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
highlight(element, animate = true) { |
|
if (!element || !element.node) { |
|
console.warn('Invalid element to highlight. Must be an instance of `Element`'); |
|
return; |
|
} |
|
|
|
|
|
element.onHighlightStarted(); |
|
|
|
|
|
if (this.highlightedElement) { |
|
this.highlightedElement.onDeselected(); |
|
} |
|
|
|
|
|
const position = element.getCalculatedPosition(); |
|
if (!position.canHighlight()) { |
|
return; |
|
} |
|
|
|
this.lastHighlightedElement = this.highlightedElement; |
|
this.highlightedElement = element; |
|
this.positionToHighlight = position; |
|
|
|
|
|
|
|
if (!this.options.animate || !animate) { |
|
this.highlightedPosition = this.positionToHighlight; |
|
} |
|
|
|
this.draw(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
getHighlightedElement() { |
|
return this.highlightedElement; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
getLastHighlightedElement() { |
|
return this.lastHighlightedElement; |
|
} |
|
|
|
|
|
|
|
|
|
clear() { |
|
this.positionToHighlight = new Position(); |
|
if (this.highlightedElement) { |
|
this.highlightedElement.onDeselected(); |
|
} |
|
|
|
this.highlightedElement = null; |
|
this.lastHighlightedElement = 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 - this.window.scrollX - this.options.padding, |
|
posY: this.highlightedPosition.top - this.window.scrollY - this.options.padding, |
|
width: (this.highlightedPosition.right - this.highlightedPosition.left) + (this.options.padding * 2), |
|
height: (this.highlightedPosition.bottom - this.highlightedPosition.top) + (this.options.padding * 2), |
|
}); |
|
|
|
|
|
if (canHighlight) { |
|
if (!this.options.animate) { |
|
this.overlayAlpha = this.options.opacity; |
|
} else { |
|
this.overlayAlpha += (this.options.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) { |
|
this.document.body.appendChild(this.overlay); |
|
} |
|
|
|
|
|
|
|
if (!this.hasPositionHighlighted()) { |
|
this.redrawAnimation = this.window.requestAnimationFrame(this.draw); |
|
} else if (!this.options.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.options.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(); |
|
} |
|
} |
|
} |
|
|