File size: 5,711 Bytes
22c7264 bcf9be5 f898881 bcf9be5 5829a93 22c7264 37f5ac4 22c7264 37f5ac4 22c7264 fe79342 22c7264 bcf9be5 22c7264 bcf9be5 fe79342 22c7264 37f5ac4 f898881 37f5ac4 bcf9be5 22c7264 bcf9be5 22c7264 bcf9be5 22c7264 f898881 22c7264 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
import Position from './position';
/**
* Responsible for overlay creation and manipulation i.e.
* cutting out the visible part, animating between the sections etc
*/
export default class Overlay {
constructor({ opacity = 0.75 }) {
this.opacity = opacity;
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();
}
// Prepares the overlay
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';
}
// Highlights the dom element on the screen
highlight(element, animate = true) {
if (!element) {
return;
}
// get the position of element around which we need to draw
const position = element.getPosition();
if (!position.canHighlight()) {
return;
}
this.highlightedElement = element;
this.positionToHighlight = position;
// If animation is not required then set the last path to be same
// as the current path so that there is no easing towards it
if (!animate) {
this.highlightedPosition = this.positionToHighlight;
}
this.draw();
}
clear() {
// Cancel the existing animation frame if any
// remove the highlighted element and remove the canvas
this.window.cancelAnimationFrame(this.redrawAnimation);
this.highlightedElement = null;
this.document.body.removeChild(this.overlay);
}
/**
* `draw` is called for in requestAnimationFrame. Here is what it does
* - Puts back the filled overlay on body (i.e. while clearing existing highlight if any)
* - Slowly eases towards the item to be selected
*/
draw() {
// Cache the response of this for re-use below
const canHighlight = this.positionToHighlight.canHighlight();
// Remove the existing cloak from the body
// it might be torn i.e. have patches from last highlight
this.removeCloak();
// Add the overlay on top of the whole body
this.addCloak();
if (canHighlight) {
const isFadingIn = this.overlayAlpha < 0.1;
if (isFadingIn) {
// Ignore the animation, just highlight the item
this.highlightedPosition = this.positionToHighlight;
} else {
// Slowly move towards the position to highlight
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;
}
}
// Remove the cloak from the position to highlight
this.removeCloak({
posX: this.highlightedPosition.left - window.scrollX - 5,
posY: this.highlightedPosition.top - window.scrollY - 5,
width: (this.highlightedPosition.right - this.highlightedPosition.left) + (5 * 2),
height: (this.highlightedPosition.bottom - this.highlightedPosition.top) + (5 * 2),
});
if (canHighlight) {
// Fade the overlay in if we can highlight
this.overlayAlpha += (this.opacity - this.overlayAlpha) * 0.08;
} else {
// otherwise fade out
this.overlayAlpha = Math.max((this.overlayAlpha * 0.85) - 0.02, 0);
}
// cancel any existing animation frames
// to avoid the overlapping of frames
this.window.cancelAnimationFrame(this.redrawAnimation);
// Continue drawing while we can highlight or we are still fading out
if (canHighlight || this.overlayAlpha > 0) {
// Add the overlay if not already there
if (!this.overlay.parentNode) {
document.body.appendChild(this.overlay);
}
// Stage a new animation frame
this.redrawAnimation = this.window.requestAnimationFrame(this.draw);
} else {
this.document.body.removeChild(this.overlay);
}
}
// Removes the patch of given width and height from the given position (x,y)
removeCloak({
posX = 0,
posY = 0,
width = this.overlay.width,
height = this.overlay.height,
} = {}) {
this.context.clearRect(posX, posY, width, height);
}
// Adds the cloak i.e. covers the given position with dark overlay
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) {
// By default it is going to cover the whole page and then we will
// cut out a chunk for the element to be visible out of it
this.overlay.width = width || this.window.innerWidth;
this.overlay.height = height || this.window.innerHeight;
// If the highlighted element was there Cancel the
// existing animation frame if any and highlight it again
// as its position might have been changed
if (this.highlightedElement) {
this.window.cancelAnimationFrame(this.redrawAnimation);
this.highlight(this.highlightedElement);
}
}
}
|