Created
October 7, 2025 09:12
-
-
Save neongreen/b9303670ea854b72b60cc7763fc921c1 to your computer and use it in GitHub Desktop.
memory killer (i had to restart my mac)
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
| // App.jsx | |
| import { useEffect, useMemo, useRef, useState } from "react"; | |
| import "./styles.css"; | |
| export default function App() { | |
| const [running, setRunning] = useState(false); | |
| const [slider, setSlider] = useState(30); // 0..100 exponential | |
| const [backdrop, setBackdrop] = useState(true); | |
| const [shadows, setShadows] = useState(true); | |
| const [bigLayers, setBigLayers] = useState(true); | |
| const bucketRef = useRef(null); | |
| const timerRef = useRef(null); | |
| const styleRef = useRef(null); | |
| // exponential batches per tick | |
| const batch = useMemo(() => Math.max(1, Math.floor(Math.pow(2, slider / 7))), [slider]); | |
| // element size scales gently to push larger layer backing stores | |
| const baseSide = useMemo(() => 256 + Math.floor(slider / 100 * 1024), [slider]); // 256..1280 | |
| // very long durations to keep cpu low but keep layers alive | |
| const dur = useMemo(() => 20 + Math.floor(slider / 2), [slider]); // seconds | |
| useEffect(() => { | |
| // dynamic stylesheet keeps big rules outside inline attrs to avoid JS churn each frame | |
| const style = document.createElement("style"); | |
| style.id = "dom-stress-style"; | |
| style.textContent = ` | |
| :root { --dur:${dur}s } | |
| /* slow animations that still allocate compositor layers */ | |
| @keyframes pan { from { transform: translate3d(0,0,0) } to { transform: translate3d(8px,6px,0) } } | |
| @keyframes pulse { from { opacity:.92 } to { opacity:.98 } } | |
| @keyframes bg { from { background-position:0 0 } to { background-position:2048px 2048px } } | |
| .layer { | |
| position: fixed; | |
| /* spread across screen randomly to force many visible layers */ | |
| top: 0; left: 0; | |
| will-change: transform, opacity, filter; | |
| animation: | |
| pan var(--dur) linear infinite alternate, | |
| pulse calc(var(--dur) * 1.3) ease-in-out infinite alternate; | |
| pointer-events: none; | |
| contain: layout paint style; | |
| mix-blend-mode: multiply; | |
| } | |
| .layer.big { width: 80vmin; height: 80vmin } | |
| .layer.mid { width: 48vmin; height: 48vmin } | |
| .layer.small { width: 28vmin; height: 28vmin } | |
| /* heavy backgrounds that create large raster surfaces without JS buffers */ | |
| .grad { | |
| background-image: | |
| radial-gradient(circle at 20% 30%, rgba(99,102,241,.25), transparent 60%), | |
| radial-gradient(circle at 80% 60%, rgba(244,114,182,.20), transparent 55%), | |
| conic-gradient(from 30deg, rgba(0,0,0,.06), transparent 20%, rgba(0,0,0,.08) 40%, transparent 60%, rgba(0,0,0,.06) 80%, transparent); | |
| background-size: 1024px 1024px, 1536px 1536px, 2048px 2048px; | |
| animation: bg calc(var(--dur) * 2) linear infinite; | |
| } | |
| /* optional big shadow lists to balloon paint memory */ | |
| .shadowy { | |
| box-shadow: | |
| 0 0 0 1px rgba(0,0,0,.04), | |
| 0 4px 8px rgba(0,0,0,.07), | |
| 0 8px 20px rgba(0,0,0,.06), | |
| 0 18px 38px rgba(0,0,0,.05), | |
| 0 36px 64px rgba(0,0,0,.05); | |
| filter: saturate(1.03) contrast(1.02); | |
| } | |
| /* backdrop layers trigger separate backing stores per element on supporting browsers */ | |
| .glass { | |
| background: rgba(255,255,255,.06); | |
| -webkit-backdrop-filter: blur(6px) saturate(1.1); | |
| backdrop-filter: blur(6px) saturate(1.1); | |
| } | |
| /* dense text to grow dom text nodes a bit without causing layout thrash */ | |
| .payload { position:absolute; inset: 6px; font: 12px/1.15 ui-sans-serif, system-ui; opacity:.001; user-select:none } | |
| `; | |
| document.head.appendChild(style); | |
| styleRef.current = style; | |
| return () => { style.remove(); styleRef.current = null; }; | |
| }, [dur]); | |
| useEffect(() => { | |
| if (!running) { | |
| clearInterval(timerRef.current); | |
| timerRef.current = null; | |
| return; | |
| } | |
| const bucket = bucketRef.current; | |
| // create batches on a gentle interval to avoid script load | |
| timerRef.current = setInterval(() => { | |
| const frag = document.createDocumentFragment(); | |
| for (let i = 0; i < batch; i++) { | |
| const el = document.createElement("div"); | |
| // choose a size class to vary layer dimensions | |
| const cls = i % 3 === 0 ? "big" : i % 3 === 1 ? "mid" : "small"; | |
| el.className = `layer grad ${cls} ${shadows ? "shadowy" : ""} ${backdrop ? "glass" : ""}`; | |
| // randomize position via inline to avoid selector reuse and layer sharing | |
| const vw = Math.max(100, window.innerWidth); | |
| const vh = Math.max(100, window.innerHeight); | |
| const x = Math.floor(Math.random() * vw); | |
| const y = Math.floor(Math.random() * vh); | |
| el.style.transform = `translate3d(${x}px, ${y}px, 0)`; | |
| if (bigLayers) { | |
| // optionally force big fixed pixel sizes rather than vmin | |
| const side = baseSide + ((i * 31) % 256); | |
| el.style.width = side + "px"; | |
| el.style.height = side + "px"; | |
| } | |
| // a tiny hidden text payload increases dom memory without work | |
| const p = document.createElement("div"); | |
| p.className = "payload"; | |
| p.textContent = "x".repeat(4000 + (i % 2000)); | |
| el.appendChild(p); | |
| frag.appendChild(el); | |
| } | |
| bucket.appendChild(frag); | |
| }, 200); // low frequency so cpu stays chilled | |
| return () => { clearInterval(timerRef.current); timerRef.current = null; }; | |
| }, [running, batch, baseSide, backdrop, shadows, bigLayers]); | |
| function start() { setRunning(true); } | |
| function stop() { setRunning(false); } | |
| function panic() { | |
| setRunning(false); | |
| clearInterval(timerRef.current); | |
| timerRef.current = null; | |
| if (bucketRef.current) bucketRef.current.innerHTML = ""; | |
| } | |
| return ( | |
| <div className="App"> | |
| <h1>dom layer memory eater</h1> | |
| <p className="warn">creates many large compositor layers with slow css animations and optional backdrop blur</p> | |
| <label className="row"> | |
| <span>growth</span> | |
| <input type="range" min={0} max={100} value={slider} onChange={(e)=>setSlider(+e.target.value)} /> | |
| <code>batch≈{batch}</code> | |
| <code>side≈{baseSide}px</code> | |
| <code>dur≈{dur}s</code> | |
| </label> | |
| <div className="row"> | |
| <label><input type="checkbox" checked={bigLayers} onChange={e=>setBigLayers(e.target.checked)} /> big fixed pixel layers</label> | |
| <label><input type="checkbox" checked={shadows} onChange={e=>setShadows(e.target.checked)} /> heavy shadows</label> | |
| <label title="requires browser support"><input type="checkbox" checked={backdrop} onChange={e=>setBackdrop(e.target.checked)} /> backdrop blur</label> | |
| </div> | |
| <div className="row"> | |
| {!running ? <button className="start" onClick={start}>start</button> : <button className="stop" onClick={stop}>stop</button>} | |
| <button className="panic" onClick={panic}>panic</button> | |
| </div> | |
| <div className="bucket" ref={bucketRef} /> | |
| </div> | |
| ); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment