| 1 | // Wrap all DOM-dependent code inside a DOMContentLoaded event listener |
| 2 | document.addEventListener('DOMContentLoaded', function() { |
| 3 | // Get the canvas element and its 2D rendering context |
| 4 | const canvas = document.getElementById('pixelCanvas'); |
| 5 | const ctx = canvas.getContext('2d'); |
| 6 | |
| 7 | // Text content and properties for the canvas-drawn text |
| 8 | const mainText = "Dynamic Pixelmap Background"; |
| 9 | const subText = "Watch the colors change!"; |
| 10 | const thirdText = "This text has pixel-level contrast!"; |
| 11 | let fontSizeH1 = 48; // px - will be dynamically adjusted |
| 12 | let fontSizeP = 24; // px - will be dynamically adjusted |
| 13 | const fontName = "Inter, sans-serif"; // Ensure font is available or fallback |
| 14 | |
| 15 | /** |
| 16 | * Generates a random hexadecimal color code, biased towards darker shades (slightly brighter than before). |
| 17 | * |
| 18 | * @returns {string} A random hexadecimal color code (e.g., '#RRGGBB'). |
| 19 | */ |
| 20 | function generateRandomColor() { |
| 21 | // Limit random byte generation to a darker range (0-150) - adjusted for brighter background |
| 22 | const getRandomDarkByte = () => Math.floor(Math.random() * 151); // Max value 150 |
| 23 | |
| 24 | const r = getRandomDarkByte(); |
| 25 | const g = getRandomDarkByte(); |
| 26 | const b = getRandomDarkByte(); |
| 27 | |
| 28 | // Helper to convert a number to a two-digit hexadecimal string |
| 29 | const toHex = (c) => { |
| 30 | const hex = c.toString(16); |
| 31 | return hex.length === 1 ? '0' + hex : hex; // Ensure two digits (e.g., 'f' becomes '0f') |
| 32 | }; |
| 33 | |
| 34 | // Combine RGB components into a full hexadecimal color string |
| 35 | return `#${toHex(r)}${toHex(g)}${toHex(b)}`; |
| 36 | } |
| 37 | |
| 38 | // Function to set up canvas dimensions and handle window resizing |
| 39 | function resizeCanvas() { |
| 40 | canvas.width = window.innerWidth; |
| 41 | canvas.height = window.innerHeight; |
| 42 | |
| 43 | // Dynamically adjust font sizes based on screen width for responsiveness |
| 44 | if (canvas.width < 600) { |
| 45 | fontSizeH1 = 32; // Smaller font for small screens |
| 46 | fontSizeP = 18; |
| 47 | } else if (canvas.width < 900) { |
| 48 | fontSizeH1 = 40; // Medium font for medium screens |
| 49 | fontSizeP = 22; |
| 50 | } else { |
| 51 | fontSizeH1 = 48; // Original font size for large screens |
| 52 | fontSizeP = 24; |
| 53 | } |
| 54 | // No need to clear here, animate will clear and redraw |
| 55 | } |
| 56 | |
| 57 | // Define parameters for drawing background pixels |
| 58 | const pixelSize = 1; // The size (width and height) of each square pixel in pixels (remains 1) |
| 59 | const pixelsPerFrame = 500; // The number of random background pixels to draw in each animation frame (increased for quicker change) |
| 60 | |
| 61 | /** |
| 62 | * Draws a single randomly colored pixel on the canvas. |
| 63 | * The pixel's position is snapped to a grid defined by `pixelSize`. |
| 64 | */ |
| 65 | function drawRandomBackgroundPixel() { |
| 66 | const x = Math.floor(Math.random() * (canvas.width / pixelSize)) * pixelSize; |
| 67 | const y = Math.floor(Math.random() * (canvas.height / pixelSize)) * pixelSize; |
| 68 | const color = generateRandomColor(); |
| 69 | ctx.fillStyle = color; |
| 70 | ctx.fillRect(x, y, pixelSize, pixelSize); |
| 71 | } |
| 72 | |
| 73 | /** |
| 74 | * Draws text on the canvas with pixel-level contrast to the background. |
| 75 | * This function uses an off-screen canvas to get the text mask. |
| 76 | */ |
| 77 | function drawContrastingText() { |
| 78 | // Create an off-screen canvas for text rendering |
| 79 | const tempCanvas = document.createElement('canvas'); |
| 80 | const tempCtx = tempCanvas.getContext('2d'); |
| 81 | tempCanvas.width = canvas.width; |
| 82 | tempCanvas.height = canvas.height; |
| 83 | |
| 84 | // Set text properties for the off-screen canvas |
| 85 | tempCtx.font = `${fontSizeH1}px ${fontName}`; |
| 86 | tempCtx.textAlign = 'center'; |
| 87 | tempCtx.textBaseline = 'middle'; |
| 88 | tempCtx.fillStyle = 'black'; // Draw text in black to get a clear mask |
| 89 | |
| 90 | // Calculate text positions (centered) |
| 91 | const centerX = canvas.width / 2; |
| 92 | const centerY = canvas.height / 2; |
| 93 | // Adjust lineHeight based on current fontSizeH1 |
| 94 | const lineHeight = fontSizeH1 * 1.2; // Approximate line height |
| 95 | |
| 96 | // Draw text on the off-screen canvas |
| 97 | tempCtx.fillText(mainText, centerX, centerY - lineHeight); |
| 98 | tempCtx.font = `${fontSizeP}px ${fontName}`; |
| 99 | tempCtx.fillText(subText, centerX, centerY); |
| 100 | tempCtx.fillText(thirdText, centerX, centerY + lineHeight); |
| 101 | |
| 102 | // Get pixel data from the main canvas (current background) |
| 103 | const mainImageData = ctx.getImageData(0, 0, canvas.width, canvas.height); |
| 104 | const mainPixels = mainImageData.data; |
| 105 | |
| 106 | // Get pixel data from the off-screen text canvas (text mask) |
| 107 | const tempImageData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height); |
| 108 | const tempPixels = tempImageData.data; |
| 109 | |
| 110 | // Loop through all pixels |
| 111 | for (let i = 0; i < mainPixels.length; i += 4) { |
| 112 | // Check if the pixel on the temporary canvas is part of the text (opaque black) |
| 113 | if (tempPixels[i + 3] > 0) { // If this pixel is part of the text |
| 114 | // Get the background color at this pixel position from main canvas |
| 115 | const bgR = mainPixels[i]; |
| 116 | const bgG = mainPixels[i + 1]; |
| 117 | const bgB = mainPixels[i + 2]; |
| 118 | |
| 119 | // Calculate the inverse color for contrast. |
| 120 | mainPixels[i] = 255 - bgR; |
| 121 | mainPixels[i + 1] = 255 - bgG; |
| 122 | mainPixels[i + 2] = 255 - bgB; |
| 123 | } |
| 124 | } |
| 125 | |
| 126 | // Put the modified pixel data back onto the main canvas |
| 127 | ctx.putImageData(mainImageData, 0, 0); |
| 128 | } |
| 129 | |
| 130 | /** |
| 131 | * The main animation loop. |
| 132 | * It draws a set number of random background pixels per frame and then redraws the contrasting text. |
| 133 | */ |
| 134 | function animate() { |
| 135 | // Draw multiple background pixels in each frame (accumulating effect) |
| 136 | for (let i = 0; i < pixelsPerFrame; i++) { |
| 137 | drawRandomBackgroundPixel(); |
| 138 | } |
| 139 | |
| 140 | // Draw the contrasting text on top of the background |
| 141 | drawContrastingText(); |
| 142 | |
| 143 | // Request the next animation frame, creating a smooth loop |
| 144 | requestAnimationFrame(animate); |
| 145 | } |
| 146 | |
| 147 | // Initial setup and animation start |
| 148 | resizeCanvas(); // Set the initial canvas size and font sizes |
| 149 | animate(); // Start the pixel drawing animation |
| 150 | |
| 151 | // Add an event listener to resize the canvas whenever the window is resized |
| 152 | window.addEventListener('resize', () => { |
| 153 | resizeCanvas(); // Recalculate canvas size and font sizes |
| 154 | // On resize, you might want to clear the canvas and immediately redraw text |
| 155 | // to avoid temporary distortions before the pixels fill in. |
| 156 | // ctx.clearRect(0, 0, canvas.width, canvas.height); // Optional: clear on resize |
| 157 | }); |
| 158 | |
| 159 | window.onload = () => setTimeout(wait,5000); |
| 160 | function wait(){alert("🎉 Surprise! Welcome to the digital party! 🥳")} |
| 161 | }); |