1(() => { 2 "use strict"; 3 4 const canvas = document.querySelector("canvas.matrix-bg"); 5 if (!(canvas instanceof HTMLCanvasElement)) { 6 return; 7 } 8 9 const context = canvas.getContext("2d"); 10 if (!context) { 11 return; 12 } 13 14 const MAX_PIXEL_RATIO = 2; 15 const FONT_FAMILY = '"Courier New", Courier, monospace, "Microsoft YaHei", "PingFang SC", sans-serif'; 16 const HEX_GLYPHS = "0123456789ABCDEF"; 17 const HAN_GLYPHS = String.fromCodePoint( 18 0x7535, 0x8111, 0x7f51, 0x7edc, 0x6570, 0x636e, 0x7cfb, 0x7edf, 19 0x7801, 0x673a, 0x82af, 0x7247, 0x7b97, 0x6cd5, 0x865a, 0x62df, 20 0x4e91, 0x667a, 0x80fd, 0x6e90, 21 ); 22 const COLORS = ["#00ff41", "#bd00ff", "#00f0ff", "#ff38b6"]; 23 const STREAM_GROUPS = [ 24 { count: 34, glyphCount: 52, duration: [64, 90], blur: [3, 5], opacity: [0.2, 0.35], size: [0.6, 0.85] }, 25 { count: 37, glyphCount: 52, duration: [128, 172], blur: [3, 5], opacity: [0.2, 0.35], size: [0.6, 0.85] }, 26 { count: 18, glyphCount: 26, duration: [42, 58], blur: [2, 3], opacity: [0.4, 0.5], size: [0.9, 1] }, 27 { count: 17, glyphCount: 26, duration: [84, 116], blur: [2, 3], opacity: [0.4, 0.5], size: [0.9, 1] }, 28 { count: 10, glyphCount: 16, duration: [28, 38], blur: [6, 8], opacity: [0.2, 0.3], size: [1.1, 1.3] }, 29 { count: 10, glyphCount: 16, duration: [56, 72], blur: [6, 8], opacity: [0.2, 0.3], size: [1.1, 1.3] }, 30 ]; 31 32 let randomState = 0x1b873593; 33 let viewportWidth = 0; 34 let viewportHeight = 0; 35 let pixelRatio = 1; 36 let rootFontSize = 16; 37 let animationFrame = 0; 38 39 function random() { 40 randomState += 0x6d2b79f5; 41 let value = randomState; 42 value = Math.imul(value ^ (value >>> 15), value | 1); 43 value ^= value + Math.imul(value ^ (value >>> 7), value | 61); 44 return ((value ^ (value >>> 14)) >>> 0) / 4294967296; 45 } 46 47 function randomBetween([minimum, maximum]) { 48 return minimum + (maximum - minimum) * random(); 49 } 50 51 function randomInteger([minimum, maximum]) { 52 return Math.floor(randomBetween([minimum, maximum + 1])); 53 } 54 55 function createGlyphs(length) { 56 let glyphs = ""; 57 58 for (let index = 0; index < length; index += 1) { 59 const alphabet = index % 4 === 3 ? HAN_GLYPHS : HEX_GLYPHS; 60 glyphs += alphabet[Math.floor(random() * alphabet.length)]; 61 } 62 63 return glyphs; 64 } 65 66 function createStreams() { 67 const streams = []; 68 69 for (const group of STREAM_GROUPS) { 70 for (let index = 0; index < group.count; index += 1) { 71 streams.push({ 72 blur: randomInteger(group.blur), 73 color: COLORS[Math.floor(random() * COLORS.length)], 74 duration: randomBetween(group.duration) * 1000, 75 glyphs: createGlyphs(group.glyphCount), 76 opacity: randomBetween(group.opacity), 77 phase: random(), 78 size: randomBetween(group.size), 79 x: (index + random()) / group.count, 80 texture: null, 81 textureHeight: 0, 82 textureTop: 0, 83 textureWidth: 0, 84 }); 85 } 86 } 87 88 return streams; 89 } 90 91 const streams = createStreams(); 92 const motionQuery = window.matchMedia("(prefers-reduced-motion: reduce)"); 93 94 function createTexture(stream) { 95 const fontSize = Math.max(stream.size * rootFontSize, viewportWidth / 100); 96 const lineHeight = fontSize * 1.18; 97 const padding = Math.ceil(stream.blur * 3 + 2); 98 const textureWidth = Math.ceil(fontSize * 1.25 + padding * 2); 99 const textureHeight = Math.ceil(lineHeight * stream.glyphs.length + padding * 2); 100 const texture = document.createElement("canvas"); 101 102 texture.width = Math.ceil(textureWidth * pixelRatio); 103 texture.height = Math.ceil(textureHeight * pixelRatio); 104 105 const textureContext = texture.getContext("2d"); 106 if (!textureContext) { 107 return null; 108 } 109 110 textureContext.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0); 111 textureContext.filter = `blur(${stream.blur}px)`; 112 textureContext.fillStyle = stream.color; 113 textureContext.font = `700 ${fontSize}px ${FONT_FAMILY}`; 114 textureContext.globalAlpha = stream.opacity; 115 textureContext.textAlign = "center"; 116 textureContext.textBaseline = "top"; 117 118 for (let index = 0; index < stream.glyphs.length; index += 1) { 119 textureContext.fillText(stream.glyphs[index], textureWidth / 2, padding + index * lineHeight); 120 } 121 122 stream.textureHeight = textureHeight; 123 stream.textureTop = padding; 124 stream.textureWidth = textureWidth; 125 return texture; 126 } 127 128 function render(timestamp) {
129 context.clearRect(0, 0, viewportWidth, viewportHeight); 130 131 const travelDistance = viewportHeight * 3; 132 const staticTop = viewportHeight * 0.2; 133 134 for (const stream of streams) { 135 if (!stream.texture) { 136 continue; 137 } 138 139 const top = motionQuery.matches 140 ? staticTop 141 : -viewportHeight * 1.5 + ((timestamp / stream.duration + stream.phase) % 1) * travelDistance; 142 143 context.drawImage( 144 stream.texture, 145 stream.x * viewportWidth - stream.textureWidth / 2, 146 top - stream.textureTop, 147 stream.textureWidth, 148 stream.textureHeight, 149 ); 150 } 151 } 152 153 function resize() { 154 const bounds = canvas.getBoundingClientRect(); 155 const nextWidth = Math.max(1, Math.round(bounds.width)); 156 const nextHeight = Math.max(1, Math.round(bounds.height)); 157 const nextPixelRatio = Math.min(window.devicePixelRatio || 1, MAX_PIXEL_RATIO); 158 159 if ( 160 nextWidth === viewportWidth 161 && nextHeight === viewportHeight 162 && nextPixelRatio === pixelRatio 163 ) { 164 return; 165 } 166 167 viewportWidth = nextWidth; 168 viewportHeight = nextHeight; 169 pixelRatio = nextPixelRatio; 170 rootFontSize = Number.parseFloat(getComputedStyle(document.documentElement).fontSize) || 16; 171 canvas.width = Math.ceil(viewportWidth * pixelRatio); 172 canvas.height = Math.ceil(viewportHeight * pixelRatio); 173 context.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0); 174 175 for (const stream of streams) { 176 stream.texture = createTexture(stream); 177 } 178 179 render(performance.now()); 180 } 181 182 function scheduleFrame() { 183 if (animationFrame || document.hidden || motionQuery.matches) { 184 return; 185 } 186 187 animationFrame = window.requestAnimationFrame((timestamp) => { 188 animationFrame = 0; 189 render(timestamp); 190 scheduleFrame(); 191 }); 192 } 193 194 function refreshAnimation() { 195 if (animationFrame) { 196 window.cancelAnimationFrame(animationFrame); 197 animationFrame = 0; 198 } 199 200 resize(); 201 render(performance.now()); 202 scheduleFrame(); 203 } 204 205 window.addEventListener("resize", refreshAnimation, { passive: true }); 206 document.addEventListener("visibilitychange", refreshAnimation); 207 motionQuery.addEventListener("change", refreshAnimation); 208 window.requestAnimationFrame(refreshAnimation); 209})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.