Skip to content

Instantly share code, notes, and snippets.

@Slakinov
Last active August 11, 2023 00:49
Show Gist options
  • Select an option

  • Save Slakinov/c218809022f096d53240c08245cf60f3 to your computer and use it in GitHub Desktop.

Select an option

Save Slakinov/c218809022f096d53240c08245cf60f3 to your computer and use it in GitHub Desktop.

Revisions

  1. Slakinov revised this gist Jun 29, 2022. No changes.
  2. Slakinov created this gist Jun 29, 2022.
    133 changes: 133 additions & 0 deletions fritz.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,133 @@
    export default function(
    node,
    {
    duration = 1000,
    delay = 0,
    reverse = false,
    absolute = false,
    pointerEvents = true,
    }
    ) {
    // recursively find text nodes
    let textNodes = findTextNodes(node);
    let nodeLengths = textNodes.map(n => n.nodeValue.length);
    let fullText = textNodes.map(n => n.nodeValue).join('');
    let blankText = fullText.split(' ').map(e => {
    let w = '';
    for(let c = 0; c < e.length; c++) { w+=' ' } // <-- unicode non-breaking space character
    return w;
    }).join(' ');
    let bufferText = ''+blankText;
    let garbageSpread = ~~(fullText.length * (reverse ? 0.25 : 1.5));
    let garbageDensity = reverse ? 20 : 20;
    let garbageOpacity = reverse ? 0.1 : 0.8;
    let mult = reverse ? -1 : 1;
    let glitchiness = 0.5;

    // prevent content being shoved down the page when 2 transitions overlap
    if(absolute) {
    node.style.position = 'absolute';
    node.style.top = '0';
    }

    // disable clicks during transtition
    if(!pointerEvents) {
    node.style.pointerEvents = 'none';
    }

    // duration = ~~(fullText.length * 2); // fixed speed

    return {
    duration,
    delay,
    tick: t => {
    t = easeInOutSine(t);
    t = Math.pow(t, 2);
    if(reverse) t = 1-t;

    let progress = ~~(fullText.length * Math.abs(t*mult));
    let garbageWidth = ~~((0.5 - Math.abs(t-0.5)) * 2 * garbageSpread);
    let output;

    if(reverse) {
    // fill with blank text up to progress minus garbage region
    output = blankText.slice(0, Math.max(progress-1-garbageWidth, 0));
    } else {
    // fill with original text up to progress
    output = fullText.slice(0, progress);
    }

    if(Math.random() < glitchiness && t < 1 && t != 0) {
    // garbageify non-space characters beyond the extent of progress
    for(let g = 0; g < garbageDensity; g++) {
    let taper = g / garbageDensity;
    // let pos = reverse ? progress + ~~(Math.random()*garbageSpread*taper*mult) : progress + ~~((1-Math.random())*garbageSpread*taper);
    let pos = progress + ~~((1-Math.random())*garbageSpread*taper);
    if(bufferText[pos] != ' ') {
    if(Math.random() > garbageOpacity) {
    // occasionally add an original character
    bufferText = setCharAt(bufferText, pos, fullText[pos]);
    } else {
    bufferText = setCharAt(bufferText, pos, garbage(reverse));
    }
    }
    }
    }

    if(reverse) {
    // add garbage region, fill with original text
    output += bufferText.slice(Math.max(progress-1-garbageWidth, 0), Math.max(progress-1, 0));
    output += fullText.slice(Math.max(progress-1, 0));
    } else {
    // add garbage region, fill with black text
    output += bufferText.slice(progress, progress+garbageWidth);
    output += blankText.slice(progress+garbageWidth);
    }

    // fill up text nodes with output
    let pointer = 0;
    for(let n = 0; n < textNodes.length; n++) {
    textNodes[n].nodeValue = output.slice(pointer, pointer+nodeLengths[n]);
    pointer += nodeLengths[n];
    }
    }
    };
    }

    function findTextNodes(root) {
    let candidates = [];
    if(root.childNodes.length > 0) {
    root.childNodes.forEach(n => {
    if(n.nodeType == Node.TEXT_NODE) {
    if(n.nodeValue != ' ') {
    n.nodeValue = n.nodeValue.replace(/(\n|\r|\t)/gm, "");
    candidates.push(n);
    }
    } else {
    // recursion
    candidates.push(...findTextNodes(n));
    }
    });
    }
    return candidates;
    }

    const junk = '—~±§|[].+$^@*()•x%!?#';
    const reverseJunk = 'x';

    function garbage(reverse) {
    if(reverse) {
    return reverseJunk[~~(Math.random() * (reverseJunk.length))];
    } else {
    return junk[~~(Math.random() * (junk.length))];
    }
    }

    function setCharAt(str, index, chr) {
    if(index > str.length-1 || index < 0) return str;
    return str.substring(0,index) + chr + str.substring(index+1);
    }

    function easeInOutSine(x) {
    return -(Math.cos(Math.PI * x) - 1) / 2;
    }