126 lines
4.9 KiB
JavaScript
126 lines
4.9 KiB
JavaScript
/* ASCII-Shimmer: rendert einen Text als flimmerndes ASCII-Art-Textbild.
|
|
Idee: Buchstaben werden zu einer Pixelmaske, innerhalb der Maske
|
|
werden Zeichen aus einem Zeichensatz zufaellig neu gewuerfelt. */
|
|
(function () {
|
|
function initAsciiShimmer(container) {
|
|
var text = container.dataset.text || 'KILABOR';
|
|
var cell = parseInt(container.dataset.cell || '10', 10);
|
|
var intervalMs = parseInt(container.dataset.interval || '140', 10);
|
|
// Farbwelle: pro Buchstabe ein eigener Hue-Versatz, der Startpunkt
|
|
// wandert über die Zeit weiter -> Farbe laeuft Buchstabe fuer Buchstabe durch.
|
|
var hueSpeed = parseFloat(container.dataset.hueSpeed || '2.5');
|
|
var frame = 0;
|
|
var cols = 0;
|
|
|
|
var denseChars = ['$', '@', '%', '#', '&', '8', '0'];
|
|
var edgeChars = ['+', 'x', '-', '=', ':', '.', 'o'];
|
|
|
|
var canvas = document.createElement('canvas');
|
|
canvas.style.display = 'block';
|
|
container.appendChild(canvas);
|
|
var ctx = canvas.getContext('2d');
|
|
|
|
var mask = [];
|
|
var timer = null;
|
|
var cssWidth = 0;
|
|
var cssHeight = 0;
|
|
|
|
function buildMask() {
|
|
var width = container.clientWidth || 600;
|
|
var fontSize = Math.min(width / (text.length * 0.62), 160);
|
|
var height = Math.ceil(fontSize * 1.3);
|
|
cssWidth = width;
|
|
cssHeight = height;
|
|
|
|
var dpr = Math.min(window.devicePixelRatio || 1, 2);
|
|
canvas.width = Math.round(width * dpr);
|
|
canvas.height = Math.round(height * dpr);
|
|
canvas.style.width = width + 'px';
|
|
canvas.style.height = height + 'px';
|
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
|
|
var mCanvas = document.createElement('canvas');
|
|
mCanvas.width = width;
|
|
mCanvas.height = height;
|
|
var mctx = mCanvas.getContext('2d');
|
|
// Canvas bleibt transparent (alpha 0) - nur der Text bekommt Alpha > 0,
|
|
// darueber wird die Maske erkannt (kein separates Schwarz-Fuellen!).
|
|
mctx.fillStyle = '#fff';
|
|
mctx.font = "900 " + fontSize + "px 'Courier New', monospace";
|
|
mctx.textBaseline = 'middle';
|
|
mctx.textAlign = 'center';
|
|
mctx.fillText(text, width / 2, height / 2);
|
|
|
|
var imgData = mctx.getImageData(0, 0, width, height).data;
|
|
cols = Math.floor(width / cell);
|
|
var rows = Math.floor(height / cell);
|
|
var colsPerLetter = cols / text.length;
|
|
mask = [];
|
|
|
|
for (var ry = 0; ry < rows; ry++) {
|
|
for (var rx = 0; rx < cols; rx++) {
|
|
var px = Math.floor(rx * cell + cell / 2);
|
|
var py = Math.floor(ry * cell + cell / 2);
|
|
var idx = (py * width + px) * 4;
|
|
var alpha = imgData[idx + 3] / 255;
|
|
if (alpha > 0.15) {
|
|
var letterIndex = Math.min(text.length - 1, Math.floor(rx / colsPerLetter));
|
|
mask.push({ x: rx, y: ry, alpha: alpha, letter: letterIndex });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function draw() {
|
|
ctx.clearRect(0, 0, cssWidth, cssHeight);
|
|
ctx.font = cell + "px 'Courier New', monospace";
|
|
ctx.textBaseline = 'middle';
|
|
ctx.textAlign = 'center';
|
|
|
|
frame++;
|
|
var hueBase = (frame * hueSpeed) % 360;
|
|
var hueStep = 360 / text.length;
|
|
|
|
for (var i = 0; i < mask.length; i++) {
|
|
var c = mask[i];
|
|
var isDense = c.alpha > 0.7;
|
|
var set = isDense ? denseChars : edgeChars;
|
|
var char = set[(Math.random() * set.length) | 0];
|
|
var hue = (hueBase + c.letter * hueStep) % 360;
|
|
ctx.fillStyle = isDense
|
|
? 'hsl(' + hue + ', 90%, 55%)'
|
|
: 'hsla(' + hue + ', 90%, 55%, 0.4)';
|
|
ctx.fillText(char, c.x * cell + cell / 2, c.y * cell + cell / 2);
|
|
}
|
|
}
|
|
|
|
function start() {
|
|
buildMask();
|
|
draw();
|
|
timer = setInterval(draw, intervalMs);
|
|
}
|
|
|
|
function stop() {
|
|
if (timer) { clearInterval(timer); timer = null; }
|
|
}
|
|
|
|
start();
|
|
|
|
var resizeTimeout;
|
|
window.addEventListener('resize', function () {
|
|
clearTimeout(resizeTimeout);
|
|
resizeTimeout = setTimeout(function () { buildMask(); draw(); }, 200);
|
|
});
|
|
|
|
document.addEventListener('visibilitychange', function () {
|
|
if (document.hidden) { stop(); } else if (!timer) { start(); }
|
|
});
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
var containers = document.querySelectorAll('[data-ascii-shimmer]');
|
|
for (var i = 0; i < containers.length; i++) {
|
|
initAsciiShimmer(containers[i]);
|
|
}
|
|
});
|
|
})();
|