Last active
March 26, 2025 22:06
-
Star
(1,831)
You must be signed in to star a gist -
Fork
(134)
You must be signed in to fork a gist
-
-
Save OrionReed/4c3778ebc2b5026d2354359ca49077ca to your computer and use it in GitHub Desktop.
3D DOM viewer, copy-paste this into your console to visualise the DOM topographically.
This file contains 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
// 3D Dom viewer, copy-paste this into your console to visualise the DOM as a stack of solid blocks. | |
// You can also minify and save it as a bookmarklet (https://www.freecodecamp.org/news/what-are-bookmarklets/) | |
(() => { | |
const SHOW_SIDES = false; // color sides of DOM nodes? | |
const COLOR_SURFACE = true; // color tops of DOM nodes? | |
const COLOR_RANDOM = false; // randomise color? | |
const COLOR_HUE = 190; // hue in HSL (https://hslpicker.com) | |
const MAX_ROTATION = 180; // set to 360 to rotate all the way round | |
const THICKNESS = 20; // thickness of layers | |
const DISTANCE = 10000; // ¯\\_(ツ)_/¯ | |
function getRandomColor() { | |
const hue = Math.floor(Math.random() * 360); | |
const saturation = 50 + Math.floor(Math.random() * 30); | |
const lightness = 40 + Math.floor(Math.random() * 30); | |
return `hsl(${hue}, ${saturation}%, ${lightness}%)`; | |
} | |
const getDOMDepth = element => [...element.children].reduce((max, child) => Math.max(max, getDOMDepth(child)), 0) + 1; | |
const domDepthCache = getDOMDepth(document.body); | |
const getColorByDepth = (depth, hue = 0, lighten = 0) => `hsl(${hue}, 75%, ${Math.min(10 + depth * (1 + 60 / domDepthCache), 90) + lighten}%)`; | |
// Apply initial styles to the body to enable 3D perspective | |
const body = document.body; | |
body.style.overflow = "visible"; | |
body.style.transformStyle = "preserve-3d"; | |
body.style.perspective = DISTANCE; | |
const perspectiveOriginX = (window.innerWidth / 2); | |
const perspectiveOriginY = (window.innerHeight / 2); | |
body.style.perspectiveOrigin = body.style.transformOrigin = `${perspectiveOriginX}px ${perspectiveOriginY}px`; | |
traverseDOM(body, 0, 0, 0); | |
document.addEventListener("mousemove", (event) => { | |
const rotationY = (MAX_ROTATION * (1 - event.clientY / window.innerHeight) - (MAX_ROTATION / 2)); | |
const rotationX = (MAX_ROTATION * event.clientX / window.innerWidth - (MAX_ROTATION / 2)); | |
body.style.transform = `rotateX(${rotationY}deg) rotateY(${rotationX}deg)`; | |
}); | |
// Create side faces for an element to give it a 3D appearance | |
function createSideFaces(element, color) { | |
if (!SHOW_SIDES) { return } | |
const width = element.offsetWidth; | |
const height = element.offsetHeight; | |
const fragment = document.createDocumentFragment(); | |
// Helper function to create and style a face | |
const createFace = ({ width, height, transform, transformOrigin, top, left, right, bottom }) => { | |
const face = document.createElement('div'); | |
face.className = 'dom-3d-side-face'; | |
Object.assign(face.style, { | |
transformStyle: "preserve-3d", | |
backfaceVisibility: 'hidden', | |
position: 'absolute', | |
width: `${width}px`, | |
height: `${height}px`, | |
background: color, | |
transform, | |
transformOrigin, | |
overflow: 'hidden', | |
willChange: 'transform', | |
top, | |
left, | |
right, | |
bottom | |
}); | |
fragment.appendChild(face); | |
} | |
// Top face | |
createFace({ | |
width, | |
height: THICKNESS, | |
transform: `rotateX(-270deg) translateY(${-THICKNESS}px)`, | |
transformOrigin: 'top', | |
top: '0px', | |
left: '0px', | |
}); | |
// Right face | |
createFace({ | |
width: THICKNESS, | |
height, | |
transform: 'rotateY(90deg)', | |
transformOrigin: 'left', | |
top: '0px', | |
left: `${width}px` | |
}); | |
// Bottom face | |
createFace({ | |
width, | |
height: THICKNESS, | |
transform: `rotateX(-90deg) translateY(${THICKNESS}px)`, | |
transformOrigin: 'bottom', | |
bottom: '0px', | |
left: '0px' | |
}); | |
// Left face | |
createFace({ | |
width: THICKNESS, | |
height, | |
transform: `translateX(${-THICKNESS}px) rotateY(-90deg)`, | |
transformOrigin: 'right', | |
top: '0px', | |
left: '0px' | |
}); | |
element.appendChild(fragment); | |
} | |
// Recursive function to traverse child nodes, apply 3D styles, and create side faces | |
function traverseDOM(parentNode, depthLevel, offsetX, offsetY) { | |
for (let children = parentNode.childNodes, childrenCount = children.length, i = 0; i < childrenCount; i++) { | |
const childNode = children[i]; | |
if (!(1 === childNode.nodeType && !childNode.classList.contains('dom-3d-side-face'))) continue; | |
const color = COLOR_RANDOM ? getRandomColor() : getColorByDepth(depthLevel, COLOR_HUE, -5); | |
Object.assign(childNode.style, { | |
transform: `translateZ(${THICKNESS}px)`, | |
overflow: "visible", | |
backfaceVisibility: "hidden", | |
isolation: "auto", | |
transformStyle: "preserve-3d", | |
backgroundColor: COLOR_SURFACE ? color : getComputedStyle(childNode).backgroundColor, | |
willChange: 'transform', | |
}); | |
let updatedOffsetX = offsetX; | |
let updatedOffsetY = offsetY; | |
if (childNode.offsetParent === parentNode) { | |
updatedOffsetX += parentNode.offsetLeft; | |
updatedOffsetY += parentNode.offsetTop; | |
} | |
createSideFaces(childNode, color); | |
traverseDOM(childNode, depthLevel + 1, updatedOffsetX, updatedOffsetY); | |
} | |
} | |
})() |
Very cool! Just commenting to add a link to your tweet that led me here: https://x.com/OrionReedOne/status/1772934478421188620
It's cool! Love it <3
Very cool bookmarklet! Some of y'all may find my service, bookmarkl.ink, handy to distribute things like this. Here's this bookmarklet all packed up: https://bookmarkl.ink/OrionReed/4c3778ebc2b5026d2354359ca49077ca. Cheers!
Here's a slightly simpler version, would still love to figure out the draw ordering bugs (if that's even possible without control over the DOM)
(() => {
const COLOR_HUE = 190; // hue in HSL (https://hslpicker.com)
const THICKNESS = 20; // thickness of layers
const getDOMDepth = () => {
let m = 0;
const t = (e, d) => {m = Math.max(m, d); for(let c of e.children) t(c, d + 1)};
t(document.body, 0);
return m;
};
const maxDepth = getDOMDepth();
const getColorByDepth = depth => `hsl(${COLOR_HUE}, 75%, ${5 + (depth * 80 / maxDepth)}%)`;
// Rotate the document based on mouse position
document.addEventListener("pointermove", ({clientX, clientY}) => {
const rotationY = 180 * (0.5 - clientY / innerHeight);
const rotationX = 180 * (clientX / innerWidth - 0.5);
document.body.style.transform = `rotateX(${rotationY}deg) rotateY(${rotationX}deg)`;
});
// Apply initial styles to the body to enable 3D perspective
Object.assign(document.body.style, {
perspective: `${10000}px`,
overflow: "visible",
perspectiveOrigin: `${innerWidth/2}px ${innerHeight/2}px`,
transformOrigin: `${innerWidth/2}px ${innerHeight/2}px`
});
// Recursive function to traverse child nodes and apply 3D styles
const traverseDOM = (node, depthLevel) => {
// Only process element nodes
if (node.nodeType !== 1) return;
// Style current node
Object.assign(node.style, {
transform: `translateZ(${THICKNESS}px)`,
backfaceVisibility: "hidden", // helps with performance
isolation: "auto",
transformStyle: "preserve-3d",
backgroundColor: getColorByDepth(depthLevel),
});
// Traverse children
for (const childNode of node.children) {
traverseDOM(childNode, depthLevel + 1);
}
}
traverseDOM(document.body, 0);
})()
^ ths minifies into just this
(()=>{const e=(()=>{let e=0;const t=(n,i)=>{e=Math.max(e,i);for(let e of n.children)t(e,i+1)};return t(document.body,0),e})();document.addEventListener("pointermove",(({clientX:e,clientY:t})=>{const n=180*(.5-t/innerHeight),i=180*(e/innerWidth-.5);document.body.style.transform=`rotateX(${n}deg) rotateY(${i}deg)`})),Object.assign(document.body.style,{perspective:"10000px",overflow:"visible",perspectiveOrigin:`${innerWidth/2}px ${innerHeight/2}px`,transformOrigin:`${innerWidth/2}px ${innerHeight/2}px`});const t=(n,i)=>{if(1===n.nodeType){var o;Object.assign(n.style,{transform:"translateZ(20px)",backfaceVisibility:"hidden",isolation:"auto",transformStyle:"preserve-3d",backgroundColor:(o=i,`hsl(190, 75%, ${5+80*o/e}%)`)});for(const e of n.children)t(e,i+1)}};t(document.body,0)})();
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Pardon my french, but what the fuck.
edit: the video isn't embedding. basically, it causes firefox to spaz out on twitter.
link (bright flashing lights warning): https://utfs.io/f/b6f07e7a-6460-4dd0-862a-de3d6bd85152-1yk8hb.webm