window.requestAnimFrame = (function(){ return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback){ window.setTimeout(callback, 1000 / 8); }; })(); (function() { 'use strict'; // Initial setup function $(x) {return document.getElementById(x);} let $container = $("banner-textarea"); let $message = $("banner-msg"); let $animate = $("banner-msg-animate"); let $paragraph = null; const asciiEscMap = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', '`': '`' }; function needsAsciiEsc(str) { for (let i of str) { if (asciiEscMap[i]) { return true; } } return false; } function isAsciiString(str) { for (let i of str) { if (i.codePointAt(0) > 127) { return false; } } return true; } function maybeEscpDec(ch) { const cp = ch.codePointAt(0); return cp > 127 ? '&#' + cp + ';' : ch; } function escpAscii(str) { return [...str].map(x => asciiEscMap[x] || x).join(""); } function escp(text) { if (needsAsciiEsc(text)) { text = escpAscii(text); } return isAsciiString(text) ? text : [...text].map(maybeEscpDec).join(""); } // Messages setup /* MESSAGES be like: * [ {"delay": 0, "text": "message 1"}, * {"delay": 1200, "text": "message 2"}, * {"delay": 2200, "text": "message 4"}, * {"delay": 3600, "text": "message 5"}, * {"delay": 5200, "text": "message 6"} ] */ const MESSAGES = JSON.parse($animate.getAttribute('data-msg')); function scramble($element, text, options) { const defaults = { probability: 0.25, glitches: "-%¥¶!\"❏_△§*¢ ¿", blank: "", duration: text.length * 80, delay: 0.0 }; const settings = {...defaults, ...options}; function shuffle() {return (Math.random() < 0.5) ? 1 : -1;} function wrap(text, classes) {return "" + text + "";} const glitchCharacters = [...settings.glitches].map(escp); const glitchLength = glitchCharacters.length; const glitchProbability = settings.probability; const glitches = glitchCharacters.map(x => wrap(x, "pop")); const ghostCharacters = [...$element.innerText].map(escp); const ghostLength = ghostCharacters.length; const ghosts = ghostCharacters.map(x => wrap(x, "ghost")); const textCharacters = [...text].map(escp); const textLength = textCharacters.length; const order = Array.from(Array(textLength).keys()).sort(shuffle); let output = []; for (let i = 0; i < textLength; ++i) { const index = Math.floor(Math.random() * (glitchLength - 1)); const glitchCharacter = glitches[index]; const ghostCharacter = ghosts[i] || settings.blank; const character = Math.random() < glitchProbability ? glitchCharacter : ghostCharacter; output.push(character); } // Animation const duration = settings.duration; const ease = settings.ease; let start = null; const TRESH = 1000 / 8; // 8FPS, more than enough function easeInOutQuad(t) { if (t < 0.5) return 2 * t * t; return Math.min((4 - 2 * t) * t - 1, 1); } function refresh() { const now = Date.now(); if (!start) { start = now; } const elapsed = now - start; if (elapsed < TRESH) { window.requestAnimFrame(refresh); return; } if (elapsed > duration) { $element.innerHTML = text; return; } const interp = easeInOutQuad(elapsed / duration); const progress = Math.floor(interp * (textLength - 1)); for (let i = 0; i < progress; ++i) { const index = order[i]; output[index] = textCharacters[index]; } $element.innerHTML = output.join(''); window.requestAnimFrame(refresh); } setTimeout(refresh, settings.delay); } function animate() { for (let i = 0; i < MESSAGES.length; ++i) { let data = MESSAGES[i]; let element = $paragraph.item(i); element.innerText = ''; let options = { delay: data.delay }; scramble(element, data.text, options); } } function initialize() { for (let _ in MESSAGES) { let elem = document.createElement("p"); elem.className = "banner-msg-line"; $message.appendChild(elem); } $paragraph = $container.getElementsByTagName("p"); animate(); } initialize(); }).call(this);