@figliolia/ripples
Version:
WebGL ripples based on the clever work of Pim Schreurs
275 lines (274 loc) • 10.1 kB
JavaScript
"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;