What’s on our mind?

Collection of articles, design, site, and resources made by designers and publisher @Menu View

index6
class TextDesintegrator {
    constructor(el, options) {
        const defaultOptions = {
            padding: 160,
            density: 4,
            duration: 6000 
        };
        this.step = 0;
        this.count = 0;
        this.data = [];
        this.scale = 2;
        this.el = el;
        this.el.style.position = "relative";
        this.el.innerHTML = `<span class="td-wrapper">${this.el.textContent}</span>`;
        this.inner = this.el.querySelector("span");
        this.options = {
            ...defaultOptions,
            ...options
        };
        this.reverse = false;

        document.fonts.ready.then(() => {
            this.createCanvas();
            this.fillCanvas();
            this.pixelize();
            setTimeout(() => {
                this.start();
            }, 0);
        });
    }
    createCanvas() {
        const {width, height} = this.el.getBoundingClientRect();
        this.height = height;
        this.width = width;
        this.canvas = document.createElement("canvas");
        this.canvas.width = this.scale * (this.width + 2 * this.options.padding);
        this.canvas.height = this.scale * (this.height + 2 * this.options.padding);
        this.canvas.style.width = `${this.width + 2 * this.options.padding}px`;
        this.canvas.style.height = `${this.height + 2 * this.options.padding}px`;
        this.canvas.style.transform = `translate3d(${-this.options.padding}px, ${-this.options.padding}px, 0)`;
        this.context = this.canvas.getContext("2d");
        this.context.scale(this.scale, this.scale);
        this.el.append(this.canvas);
    }
    clearContext() {
        this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
    }
    fillCanvas() {
        const style = getComputedStyle(this.el);
        this.color = style.getPropertyValue("color");
        this.context.fillStyle = this.color;
        this.context.font = style.getPropertyValue("font");
        this.context.textBaseline = "ideographic";
        this.context.fillText(
            this.el.textContent,
            this.options.padding,
            (this.canvas.height / this.scale + this.height) / 2
        );
    }
    start() {
        this.t0 = 0;
        this.id = window.requestAnimationFrame((t) => this.render(t));
    }
    stop() {
        if (this.id) {
            window.cancelAnimationFrame(this.id);
        }
    }
    pixelize() {
        const {padding, density, duration} = this.options;
        for (
            let y = 0; y < this.canvas.height + 2 * padding - Math.floor(density / 2); y += density
        ) {
            for (
                let x = 0; x < this.canvas.width + 2 * padding - Math.floor(density / 2); x += density
            ) {
                const {
                    data
                } = this.context.getImageData(
                    x + Math.floor(density / 4),
                    y + Math.floor(density / 4),
                    1,
                    1
                );
                const [, , , a] = data;
                if (a > 0) {
                    this.data.push({
                        alpha: a / 255,
                        longevity: Math.min(
                            duration * 0.25 + Math.random() * duration * 0.75,
                            duration - 1
                        ),
                        x,
                        y,
                        initialX: x,
                        initialY: y,
                        finalX: x + 2 * (Math.random() - 0.5) * this.canvas.width,
                        finalY: y + 2 * (Math.random() - 0.5) * this.canvas.width
                    });
                }
            }
        }
    }

    render(timestamp) {
        if (!this.t0) {
            this.t0 = timestamp;
        }
        const elapsed = timestamp - this.t0;
        if (this.step < Math.min(500, this.options.duration * 0.5)) {
            if (this.reverse) {
                this.inner.classList.remove("td-hide");
            } else {
                this.inner.classList.add("td-hide");
            }
        }
        this.updateData();
        this.clearContext();
        for (const sq of this.data) {
            this.context.globalAlpha = sq.alpha;
            this.context.fillStyle = this.color;
            this.context.fillRect(
                sq.x / 2,
                sq.y / 2,
                this.options.density / 2,
                this.options.density / 2
            );
        }
        this.step = this.reverse ? this.options.duration - elapsed : elapsed;
        if (elapsed > this.options.duration) {
            this.onComplete();
        }
        this.id = requestAnimationFrame((t) => this.render(t));
    }
    onComplete() {
        this.reverse = !this.reverse;
        this.t0 = 0;
    }
    updateData() {
        for (const sq of this.data) {
            sq.alpha = this.calculateOpacity(sq.longevity, this.step);
            sq.x = this.calculatePosition(
                sq.initialX,
                sq.finalX,
                sq.longevity,
                this.step
            );
            sq.y = this.calculatePosition(
                sq.initialY,
                sq.finalY,
                sq.longevity,
                this.step
            );
        }
    }
    calculatePosition(xS, xE, l, x) {
        const expo = (l, x) => {
            return x < l ? 1 - Math.pow(2, 10 * (x / l) - 10) : 0;
        };
        const val = (xS - xE) * expo(l, x) + xE;
        return val;
    }
    calculateOpacity(l, x) {
        return x <= l ? 1 - Math.pow(x / l, 1) : 0;
    }
}

const h1 = document.querySelectorAll("h1 span");

h1.forEach((el) => new TextDesintegrator(el));