Created
March 11, 2026 09:43
-
-
Save pleabargain/41ba97f506a39770896c21961f67eec7 to your computer and use it in GitHub Desktop.
html pixelator for fun
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <title>Image Pixelation Slider</title> | |
| <style> | |
| :root { | |
| --bg: #101319; | |
| --panel: #181c25; | |
| --accent: #5b8aff; | |
| --accent-soft: rgba(91, 138, 255, 0.2); | |
| --text: #f5f7ff; | |
| --text-muted: #a2a9c3; | |
| --border: #262b3a; | |
| } | |
| * { | |
| box-sizing: border-box; | |
| font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; | |
| } | |
| body { | |
| margin: 0; | |
| min-height: 100vh; | |
| background: radial-gradient(circle at top, #1b2540 0, #05060a 50%, #020308 100%); | |
| color: var(--text); | |
| display: flex; | |
| align-items: stretch; | |
| justify-content: center; | |
| padding: 24px; | |
| } | |
| .app { | |
| width: 100%; | |
| max-width: 1100px; | |
| background: linear-gradient(135deg, rgba(255,255,255,0.04), rgba(255,255,255,0.01)); | |
| border-radius: 18px; | |
| border: 1px solid rgba(255,255,255,0.05); | |
| box-shadow: | |
| 0 30px 80px rgba(0,0,0,0.65), | |
| 0 0 0 1px rgba(255,255,255,0.02); | |
| padding: 20px 22px; | |
| display: grid; | |
| grid-template-columns: minmax(0, 3fr) minmax(260px, 2fr); | |
| gap: 20px; | |
| backdrop-filter: blur(20px) saturate(130%); | |
| } | |
| @media (max-width: 800px) { | |
| .app { | |
| grid-template-columns: 1fr; | |
| } | |
| } | |
| .left, .right { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 16px; | |
| } | |
| .title { | |
| font-size: 20px; | |
| font-weight: 600; | |
| letter-spacing: 0.03em; | |
| text-transform: uppercase; | |
| color: var(--text-muted); | |
| } | |
| .subtitle { | |
| font-size: 13px; | |
| color: var(--text-muted); | |
| } | |
| .canvas-wrap { | |
| position: relative; | |
| border-radius: 14px; | |
| border: 1px solid var(--border); | |
| background: radial-gradient(circle at top left, #242b3d 0, #10131b 45%, #05060a 100%); | |
| flex: 1; | |
| overflow: hidden; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| min-height: 260px; | |
| } | |
| .canvas-wrap.dropping::before { | |
| content: "Drop image here to pixelate"; | |
| position: absolute; | |
| inset: 0; | |
| border-radius: inherit; | |
| border: 2px dashed rgba(144,180,255,0.9); | |
| background: rgba(10,16,35,0.8); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| color: var(--text-muted); | |
| font-size: 13px; | |
| letter-spacing: 0.12em; | |
| text-transform: uppercase; | |
| z-index: 2; | |
| } | |
| canvas { | |
| max-width: 100%; | |
| max-height: 100%; | |
| background: #05060a; | |
| } | |
| .canvas-placeholder { | |
| text-align: center; | |
| color: var(--text-muted); | |
| pointer-events: none; | |
| padding: 24px; | |
| } | |
| .canvas-placeholder span { | |
| display: inline-block; | |
| margin-bottom: 8px; | |
| padding: 6px 10px; | |
| border-radius: 999px; | |
| font-size: 11px; | |
| letter-spacing: 0.15em; | |
| text-transform: uppercase; | |
| border: 1px solid rgba(255,255,255,0.08); | |
| background: rgba(7,11,24,0.9); | |
| } | |
| .control-panel { | |
| background: radial-gradient(circle at top left, rgba(91,138,255,0.16), rgba(15,18,30,0.95)); | |
| border-radius: 14px; | |
| border: 1px solid rgba(91,138,255,0.28); | |
| padding: 16px 15px 14px; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 12px; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .control-panel::before { | |
| content: ""; | |
| position: absolute; | |
| inset: -40%; | |
| background: | |
| radial-gradient(circle at 0% 0%, rgba(144,180,255,0.2) 0, transparent 60%), | |
| radial-gradient(circle at 100% 0%, rgba(120,105,255,0.18) 0, transparent 55%); | |
| opacity: 0.65; | |
| mix-blend-mode: soft-light; | |
| pointer-events: none; | |
| } | |
| .control-header { | |
| position: relative; | |
| z-index: 1; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: baseline; | |
| gap: 12px; | |
| } | |
| .control-header h2 { | |
| margin: 0; | |
| font-size: 15px; | |
| font-weight: 600; | |
| letter-spacing: 0.04em; | |
| text-transform: uppercase; | |
| } | |
| .badge { | |
| font-size: 10px; | |
| text-transform: uppercase; | |
| letter-spacing: 0.16em; | |
| padding: 3px 9px; | |
| border-radius: 999px; | |
| border: 1px solid rgba(255,255,255,0.25); | |
| background: rgba(7,11,24,0.7); | |
| color: var(--text-muted); | |
| } | |
| .field { | |
| position: relative; | |
| z-index: 1; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 6px; | |
| margin-top: 4px; | |
| } | |
| label { | |
| font-size: 12px; | |
| text-transform: uppercase; | |
| letter-spacing: 0.14em; | |
| color: var(--text-muted); | |
| } | |
| .file-input-row { | |
| display: flex; | |
| gap: 8px; | |
| align-items: center; | |
| flex-wrap: wrap; | |
| } | |
| input[type="file"] { | |
| font-size: 11px; | |
| color: var(--text-muted); | |
| } | |
| .slider-row { | |
| display: flex; | |
| gap: 10px; | |
| align-items: center; | |
| } | |
| input[type="range"] { | |
| flex: 1; | |
| -webkit-appearance: none; | |
| height: 5px; | |
| border-radius: 999px; | |
| background: rgba(8,11,22,0.7); | |
| outline: none; | |
| } | |
| input[type="range"]::-webkit-slider-thumb { | |
| -webkit-appearance: none; | |
| width: 14px; | |
| height: 14px; | |
| border-radius: 999px; | |
| background: var(--accent); | |
| box-shadow: 0 0 0 4px var(--accent-soft); | |
| cursor: pointer; | |
| border: 1px solid rgba(255,255,255,0.6); | |
| } | |
| input[type="range"]::-moz-range-thumb { | |
| width: 14px; | |
| height: 14px; | |
| border-radius: 999px; | |
| background: var(--accent); | |
| border: 1px solid rgba(255,255,255,0.6); | |
| box-shadow: 0 0 0 4px var(--accent-soft); | |
| cursor: pointer; | |
| } | |
| .slider-value { | |
| min-width: 58px; | |
| text-align: right; | |
| font-variant-numeric: tabular-nums; | |
| font-size: 12px; | |
| color: var(--text-muted); | |
| } | |
| .hint { | |
| font-size: 11px; | |
| color: var(--text-muted); | |
| line-height: 1.35; | |
| } | |
| .hint strong { | |
| color: var(--text); | |
| font-weight: 500; | |
| } | |
| .button-row { | |
| display: flex; | |
| gap: 8px; | |
| flex-wrap: wrap; | |
| } | |
| button { | |
| position: relative; | |
| z-index: 1; | |
| border-radius: 999px; | |
| border: 1px solid rgba(255,255,255,0.18); | |
| background: rgba(4,7,18,0.9); | |
| color: var(--text); | |
| font-size: 11px; | |
| text-transform: uppercase; | |
| letter-spacing: 0.16em; | |
| padding: 7px 14px; | |
| cursor: pointer; | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 6px; | |
| transition: background 120ms ease, transform 80ms ease, box-shadow 120ms ease, border-color 120ms ease; | |
| } | |
| button:hover { | |
| background: rgba(19,25,54,0.95); | |
| border-color: rgba(144,180,255,0.6); | |
| box-shadow: 0 0 0 1px rgba(144,180,255,0.6), 0 0 18px rgba(144,180,255,0.3); | |
| transform: translateY(-1px); | |
| } | |
| button:active { | |
| transform: translateY(0); | |
| box-shadow: none; | |
| } | |
| .button-primary { | |
| background: linear-gradient(120deg, #5b8aff, #8a7dff); | |
| border-color: transparent; | |
| box-shadow: 0 10px 30px rgba(91,138,255,0.4); | |
| } | |
| .button-primary:hover { | |
| box-shadow: 0 12px 38px rgba(91,138,255,0.6); | |
| } | |
| .testing-panel { | |
| position: relative; | |
| z-index: 1; | |
| margin-top: 4px; | |
| padding-top: 8px; | |
| border-top: 1px dashed rgba(200,210,255,0.25); | |
| display: flex; | |
| flex-direction: column; | |
| gap: 6px; | |
| } | |
| .testing-panel-title { | |
| font-size: 11px; | |
| text-transform: uppercase; | |
| letter-spacing: 0.18em; | |
| color: var(--text-muted); | |
| } | |
| .testing-note { | |
| font-size: 11px; | |
| color: var(--text-muted); | |
| } | |
| .status-line { | |
| font-size: 11px; | |
| color: var(--text-muted); | |
| font-variant-numeric: tabular-nums; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="app"> | |
| <div class="left"> | |
| <div> | |
| <div class="title">Pixelation Studio</div> | |
| <div class="subtitle">Load any image and scrub the slider to control how blocky it becomes.</div> | |
| </div> | |
| <div class="canvas-wrap"> | |
| <canvas id="canvas"></canvas> | |
| <div id="placeholder" class="canvas-placeholder"> | |
| <span>Step 1</span> | |
| <div>Choose an image on the right to begin, then drag the slider to adjust pixel size.</div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="right"> | |
| <div class="control-panel"> | |
| <div class="control-header"> | |
| <h2>Controls</h2> | |
| <div class="badge">HTML + Canvas</div> | |
| </div> | |
| <!-- File input --> | |
| <div class="field"> | |
| <label for="fileInput">Source Image</label> | |
| <div class="file-input-row"> | |
| <input type="file" id="fileInput" accept="image/*" /> | |
| <button id="loadSampleBtn" type="button">Load Sample</button> | |
| </div> | |
| <div class="hint"> | |
| <strong>Tip:</strong> Drag & drop an image onto the large area on the left, or choose a file here. | |
| </div> | |
| </div> | |
| <!-- Quick-load known files in same folder --> | |
| <div class="field"> | |
| <label>Quick load from this folder</label> | |
| <div class="button-row"> | |
| <button id="loadDgdMainBtn" type="button">Load DGD (WhatsApp)</button> | |
| <button id="loadDgdAltBtn" type="button">Load DGD (Untitled)</button> | |
| </div> | |
| <div class="hint"> | |
| These buttons try to load images that live next to <strong>pixelate.html</strong>. They work when the | |
| filenames match and you open the file directly from that folder. | |
| </div> | |
| </div> | |
| <!-- Slider --> | |
| <div class="field"> | |
| <label for="pixelSlider">Pixel size</label> | |
| <div class="slider-row"> | |
| <input | |
| id="pixelSlider" | |
| type="range" | |
| min="1" | |
| max="80" | |
| value="8" | |
| /> | |
| <div class="slider-value"> | |
| <span id="pixelValue">8</span> px | |
| </div> | |
| </div> | |
| <div class="hint"> | |
| 1 px = original detail, larger values = stronger pixelation. Changes apply live. | |
| </div> | |
| </div> | |
| <!-- Actions --> | |
| <div class="field"> | |
| <label>Actions</label> | |
| <div class="button-row"> | |
| <button id="resetBtn" type="button">Reset</button> | |
| <button id="downloadBtn" type="button" class="button-primary"> | |
| Export PNG | |
| </button> | |
| </div> | |
| <div class="status-line" id="statusLine"> | |
| Waiting for image… | |
| </div> | |
| </div> | |
| <!-- Testing / debug block --> | |
| <div class="testing-panel"> | |
| <div class="testing-panel-title">Testing presets</div> | |
| <div class="button-row"> | |
| <button data-preset="4" type="button">Soft (4 px)</button> | |
| <button data-preset="12" type="button">Medium (12 px)</button> | |
| <button data-preset="30" type="button">Heavy (30 px)</button> | |
| </div> | |
| <div class="testing-note"> | |
| These buttons are a small testing helper: they snap the slider to known values so you can quickly eyeball whether the pixelation behaves as expected. | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| const canvas = document.getElementById("canvas"); | |
| const ctx = canvas.getContext("2d"); | |
| const canvasWrap = document.querySelector(".canvas-wrap"); | |
| const fileInput = document.getElementById("fileInput"); | |
| const pixelSlider = document.getElementById("pixelSlider"); | |
| const pixelValue = document.getElementById("pixelValue"); | |
| const placeholder = document.getElementById("placeholder"); | |
| const statusLine = document.getElementById("statusLine"); | |
| const resetBtn = document.getElementById("resetBtn"); | |
| const downloadBtn = document.getElementById("downloadBtn"); | |
| const loadSampleBtn = document.getElementById("loadSampleBtn"); | |
| const loadDgdMainBtn = document.getElementById("loadDgdMainBtn"); | |
| const loadDgdAltBtn = document.getElementById("loadDgdAltBtn"); | |
| const presetButtons = document.querySelectorAll(".testing-panel [data-preset]"); | |
| let originalImage = null; // HTMLImageElement | |
| let imageLoaded = false; | |
| function setStatus(message) { | |
| statusLine.textContent = message; | |
| } | |
| function fitCanvasToImage(img) { | |
| const maxWidth = canvas.parentElement.clientWidth * window.devicePixelRatio; | |
| const maxHeight = canvas.parentElement.clientHeight * window.devicePixelRatio; | |
| let { width, height } = img; | |
| const aspect = width / height; | |
| if (width > maxWidth) { | |
| width = maxWidth; | |
| height = width / aspect; | |
| } | |
| if (height > maxHeight) { | |
| height = maxHeight; | |
| width = height * aspect; | |
| } | |
| const displayScale = 1 / window.devicePixelRatio; | |
| canvas.width = Math.round(width); | |
| canvas.height = Math.round(height); | |
| canvas.style.width = Math.round(width * displayScale) + "px"; | |
| canvas.style.height = Math.round(height * displayScale) + "px"; | |
| } | |
| function renderPixelated(pixelSize) { | |
| if (!imageLoaded || !originalImage) return; | |
| const w = canvas.width; | |
| const h = canvas.height; | |
| if (w === 0 || h === 0) return; | |
| const blockSize = Math.max(1, Math.floor(pixelSize)); | |
| const scaledW = Math.max(1, Math.floor(w / blockSize)); | |
| const scaledH = Math.max(1, Math.floor(h / blockSize)); | |
| const offscreen = document.createElement("canvas"); | |
| offscreen.width = scaledW; | |
| offscreen.height = scaledH; | |
| const offctx = offscreen.getContext("2d"); | |
| offctx.drawImage(originalImage, 0, 0, scaledW, scaledH); | |
| ctx.imageSmoothingEnabled = false; | |
| ctx.clearRect(0, 0, w, h); | |
| ctx.drawImage(offscreen, 0, 0, scaledW, scaledH, 0, 0, w, h); | |
| placeholder.style.display = "none"; | |
| setStatus(`Image loaded · pixel size: ${blockSize}px · ${w}×${h} canvas`); | |
| } | |
| function handleSliderChange() { | |
| const value = Number(pixelSlider.value); | |
| pixelValue.textContent = value.toString(); | |
| if (imageLoaded) { | |
| renderPixelated(value); | |
| } | |
| } | |
| function loadImageFromFile(file) { | |
| const reader = new FileReader(); | |
| reader.onload = (e) => { | |
| const img = new Image(); | |
| img.onload = () => { | |
| originalImage = img; | |
| imageLoaded = true; | |
| fitCanvasToImage(img); | |
| handleSliderChange(); | |
| }; | |
| img.onerror = () => { | |
| setStatus("Failed to load image. Please try another file."); | |
| }; | |
| img.src = e.target.result; | |
| }; | |
| reader.readAsDataURL(file); | |
| setStatus("Loading image…"); | |
| } | |
| function loadImageFromUrl(url, labelForStatus) { | |
| const img = new Image(); | |
| img.onload = () => { | |
| originalImage = img; | |
| imageLoaded = true; | |
| fitCanvasToImage(img); | |
| handleSliderChange(); | |
| setStatus(`Loaded ${labelForStatus || "image"} from same folder as pixelate.html.`); | |
| }; | |
| img.onerror = () => { | |
| setStatus(`Could not load ${labelForStatus || "image"} from relative path. Check filename and location.`); | |
| }; | |
| img.src = url; | |
| setStatus(`Loading ${labelForStatus || "image"}…`); | |
| } | |
| fileInput.addEventListener("change", () => { | |
| const file = fileInput.files && fileInput.files[0]; | |
| if (!file) return; | |
| if (!file.type.startsWith("image/")) { | |
| setStatus("Selected file is not an image."); | |
| return; | |
| } | |
| loadImageFromFile(file); | |
| }); | |
| // Drag & drop onto canvas area | |
| ["dragenter", "dragover"].forEach((evtName) => { | |
| canvasWrap.addEventListener(evtName, (e) => { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| canvasWrap.classList.add("dropping"); | |
| setStatus("Drop image file to load…"); | |
| }); | |
| }); | |
| ["dragleave", "drop"].forEach((evtName) => { | |
| canvasWrap.addEventListener(evtName, (e) => { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| canvasWrap.classList.remove("dropping"); | |
| }); | |
| }); | |
| canvasWrap.addEventListener("drop", (e) => { | |
| const dt = e.dataTransfer; | |
| if (!dt) return; | |
| const files = dt.files; | |
| if (!files || files.length === 0) { | |
| setStatus("No file found in drop."); | |
| return; | |
| } | |
| const file = files[0]; | |
| if (!file.type.startsWith("image/")) { | |
| setStatus("Dropped file is not an image."); | |
| return; | |
| } | |
| loadImageFromFile(file); | |
| }); | |
| pixelSlider.addEventListener("input", handleSliderChange); | |
| resetBtn.addEventListener("click", () => { | |
| pixelSlider.value = 8; | |
| handleSliderChange(); | |
| if (!imageLoaded) { | |
| setStatus("Reset slider. Waiting for image…"); | |
| } else { | |
| setStatus("Reset slider to 8px."); | |
| } | |
| }); | |
| downloadBtn.addEventListener("click", () => { | |
| if (!imageLoaded) { | |
| setStatus("Load an image before exporting."); | |
| return; | |
| } | |
| const link = document.createElement("a"); | |
| link.download = "pixelated.png"; | |
| link.href = canvas.toDataURL("image/png"); | |
| link.click(); | |
| setStatus("Exported current pixelated view as PNG."); | |
| }); | |
| presetButtons.forEach((btn) => { | |
| btn.addEventListener("click", () => { | |
| const value = Number(btn.getAttribute("data-preset")); | |
| pixelSlider.value = value; | |
| handleSliderChange(); | |
| setStatus(`Preset applied: ${value}px block size.`); | |
| }); | |
| }); | |
| // Quick-load buttons for specific images in same directory as pixelate.html | |
| if (loadDgdMainBtn) { | |
| loadDgdMainBtn.addEventListener("click", () => { | |
| loadImageFromUrl("DGDWhatsApp Image 2025-11-11 at 11.58.35 AM.png", "DGDWhatsApp image"); | |
| }); | |
| } | |
| if (loadDgdAltBtn) { | |
| loadDgdAltBtn.addEventListener("click", () => { | |
| loadImageFromUrl("DGD doing his thing Untitled.png", "DGD Untitled image"); | |
| }); | |
| } | |
| loadSampleBtn.addEventListener("click", () => { | |
| const sample = new Image(); | |
| const gradientSize = 400; | |
| const off = document.createElement("canvas"); | |
| off.width = gradientSize; | |
| off.height = gradientSize; | |
| const gctx = off.getContext("2d"); | |
| const g = gctx.createLinearGradient(0, 0, gradientSize, gradientSize); | |
| g.addColorStop(0, "#f97316"); | |
| g.addColorStop(0.3, "#eab308"); | |
| g.addColorStop(0.6, "#22c55e"); | |
| g.addColorStop(1, "#0ea5e9"); | |
| gctx.fillStyle = g; | |
| gctx.fillRect(0, 0, gradientSize, gradientSize); | |
| sample.onload = () => { | |
| originalImage = sample; | |
| imageLoaded = true; | |
| fitCanvasToImage(sample); | |
| handleSliderChange(); | |
| setStatus("Sample gradient loaded. Adjust the slider or presets."); | |
| }; | |
| sample.src = off.toDataURL("image/png"); | |
| }); | |
| pixelValue.textContent = pixelSlider.value; | |
| </script> | |
| </body> | |
| </html> | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment