UNPKG

@figliolia/ripples

Version:

WebGL ripples based on the clever work of Pim Schreurs

275 lines (274 loc) 10.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Ripples = void 0; const Animation_1 = require("./Animation"); const WebGL_1 = require("./WebGL"); class Ripples extends WebGL_1.WebGL { constructor(...options) { super(...options); this.visible = false; this.running = false; this.destroyed = false; this.initialized = false; this.updateSize = () => { const { offsetHeight, offsetWidth } = this.target; if (offsetWidth !== this.canvas.width || offsetHeight !== this.canvas.height) { this.canvas.width = offsetWidth; this.canvas.height = offsetHeight; void this.reloadImage(); } }; this.onClick = (e) => { if (!this.pointerEventsEnabled) { return; } this.dropAtPointer(e, this.dropRadius * 1.5, 0.14); }; this.onTouch = (e) => { if (!this.pointerEventsEnabled) { return; } const touches = e.changedTouches; for (let i = 0; i < touches.length; i++) { this.dropAtPointer(touches[i], this.dropRadius, 0.01); } }; this.onMouseMove = (e) => { if (!this.pointerEventsEnabled) { return; } this.dropAtPointer(e, this.dropRadius, 0.01); }; this.cacheTargetPositions(); this.setupWebGL(); } drop(x, y, radius, strength) { const { offsetHeight, offsetWidth } = this.target; const longestSide = Math.max(offsetHeight, offsetWidth); radius = radius / longestSide; const dropPosition = new Float32Array([ (2 * x - offsetWidth) / longestSide, (offsetHeight - 2 * y) / longestSide, ]); this.GL.viewport(0, 0, this.resolution, this.resolution); this.GL.bindFramebuffer(this.GL.FRAMEBUFFER, this.Textures.writeFrame); this.bindTexture(this.Textures.readTexture); this.Shaders.drop(dropPosition, radius, strength); this.Textures.drawQuad(); this.Textures.swapBufferIndices(); } show() { this.visible = true; this.canvas.style.display = "block"; this.hideCSSBackground(); } hide() { this.visible = false; this.canvas.style.display = "none"; this.restoreCSSBackground(); } pause() { this.running = false; } play() { this.running = true; } destroy() { this.target.style.zIndex = this.StyleCache.get("zIndex"); this.target.style.position = this.StyleCache.get("position"); // @ts-ignore this.GL = null; window.removeEventListener("resize", this.updateSize); this.target.removeEventListener("mousedown", this.onClick); this.target.removeEventListener("touchmove", this.onTouch); this.target.removeEventListener("touchstart", this.onTouch); this.target.removeEventListener("mousemove", this.onMouseMove); this.target.removeChild(this.canvas); this.restoreCSSBackground(); if (this.ID) { Animation_1.Animations.delete(this.ID); } this.destroyed = true; } reloadImage() { this.restoreCSSBackground(); this.StyleCache.evict("originalCSSBackgroundImage"); return this.loadImage(); } cacheTargetPositions() { this.StyleCache.initialize(this.target); this.target.style.zIndex = "0"; this.target.style.position = "relative"; } setupWebGL() { this.Textures.getBrowserExtensions(); window.addEventListener("resize", this.updateSize); this.initializeWebGL(); this.visible = true; this.running = true; this.initialized = true; this.setupPointerEvents(); this.ID = Animation_1.Animations.register(() => { if (!this.destroyed) { this.step(); } }); } setupPointerEvents() { this.target.addEventListener("mousedown", this.onClick); this.target.addEventListener("mousemove", this.onMouseMove); this.target.addEventListener("touchmove", this.onTouch, { passive: true }); this.target.addEventListener("touchstart", this.onTouch, { passive: true }); } get pointerEventsEnabled() { return this.visible && this.running && this.interactive; } step() { if (!this.visible) { return; } this.computeTextureBoundaries(); if (this.running) { this.update(); } this.render(); } computeBackgroundSize(backgroundSize, container) { const BW = this.StyleCache.get("backgroundWidth"); const BH = this.StyleCache.get("backgroundHeight"); if (backgroundSize == "cover") { const scale = Math.max(container.width / BW, container.height / BH); return [BW * scale, BH * scale]; } if (backgroundSize == "contain") { const scale = Math.min(container.width / BW, container.height / BH); return [BW * scale, BH * scale]; } const BP = backgroundSize.split(" "); let backgroundWidth = BP[0] || ""; let backgroundHeight = BP[1] || backgroundWidth; if (this.isPercentage(backgroundWidth)) { backgroundWidth = (container.width * parseFloat(backgroundWidth)) / 100; } else if (backgroundWidth != "auto") { backgroundWidth = parseFloat(backgroundWidth); } if (this.isPercentage(backgroundHeight)) { backgroundHeight = (container.height * parseFloat(backgroundHeight)) / 100; } else if (backgroundHeight != "auto") { backgroundHeight = parseFloat(backgroundHeight); } if (backgroundWidth == "auto" && backgroundHeight == "auto") { backgroundWidth = BW; backgroundHeight = BH; } else { if (backgroundWidth == "auto") { backgroundWidth = BW * (backgroundHeight / BH); } if (backgroundHeight == "auto") { backgroundHeight = BH * (backgroundWidth / BW); } } return [backgroundWidth, backgroundHeight]; } computeTextureBoundaries() { const { backgroundSize, backgroundAttachment, backgroundPosition: BP, } = window.getComputedStyle(this.target); const backgroundPosition = this.translateBackgroundPosition(BP); let container; if (backgroundAttachment == "fixed") { container = { left: window.scrollX, top: window.scrollY, width: window.innerWidth, height: window.innerHeight, }; } else { const { offsetTop, offsetLeft, offsetHeight, offsetWidth } = this.target; container = { top: offsetTop, left: offsetLeft, width: offsetWidth, height: offsetHeight, }; } const [width, height] = this.computeBackgroundSize(backgroundSize, container); let [x, y] = backgroundPosition; if (this.isPercentage(x)) { x = container.left + ((container.width - width) * parseFloat(x)) / 100; } else { x = container.left + parseFloat(x); } if (this.isPercentage(y)) { y = container.top + ((container.height - height) * parseFloat(y)) / 100; } else { y = container.top + parseFloat(y); } const { offsetLeft, offsetTop, offsetHeight, offsetWidth } = this.target; const floats = new Float32Array([ (offsetLeft - x) / width, (offsetTop - y) / height, ]); this.Shaders.renderProgram.uniforms.topLeft = floats; this.Shaders.renderProgram.uniforms.bottomRight = new Float32Array([ floats[0] + offsetWidth / width, floats[1] + offsetHeight / height, ]); const maxSide = Math.max(this.canvas.width, this.canvas.height); this.Shaders.renderProgram.uniforms.containerRatio = new Float32Array([ this.canvas.width / maxSide, this.canvas.height / maxSide, ]); } translateBackgroundPosition(value) { const parts = value.split(" "); if (parts.length === 1) { switch (value) { case "center": return ["50%", "50%"]; case "top": return ["50%", "0"]; case "bottom": return ["50%", "100%"]; case "left": return ["0", "50%"]; case "right": return ["100%", "50%"]; default: return [value, "50%"]; } } return parts.map(part => { switch (value) { case "center": return "50%"; case "top": case "left": return "0"; case "right": case "bottom": return "100%"; default: return part; } }); } restoreCSSBackground() { this.target.style.backgroundImage = this.StyleCache.get("originalInlineCSS") || ""; } dropAtPointer(pointer, radius, strength) { const { borderTopWidth, borderLeftWidth } = window.getComputedStyle(this.target); const borderTop = parseInt(borderTopWidth || "0"); const borderLeft = parseInt(borderLeftWidth || "0"); const { offsetTop, offsetLeft } = this.target; this.drop(pointer.pageX - offsetLeft - borderLeft, pointer.pageY - offsetTop - borderTop, radius, strength); } } exports.Ripples = Ripples;