Skip to content

Instantly share code, notes, and snippets.

@OrionReed
Last active December 9, 2024 07:36
Show Gist options
  • Save OrionReed/4c3778ebc2b5026d2354359ca49077ca to your computer and use it in GitHub Desktop.
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.
// 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);
}
}
})()
@ylluminate
Copy link

Everyone: since @OrionReed is now developing browser extensions (https://github.com/OrionReed/dom3d), it might be best to file issues at https://github.com/OrionReed/dom3d/issues - even if they are referenced here.

I think it might make it easier to track and resolve specific things.

@OrionReed
Copy link
Author

@ylluminate ^ absolutely. we can push improvement from that repo into this single-function gist and reference those issues/changes here.

@malikalimoekhamedov
Copy link

This should be a browser plugin.

@swnck
Copy link

swnck commented Mar 29, 2024

I have a updated version without the blue color:
https://gist.github.com/swnck/7aa80d2968a115ada8d920d772823cc8

@OrionReed
Copy link
Author

@malikalimoekhamedov it is being plugin-ified here here

@malikalimoekhamedov
Copy link

@malikalimoekhamedov it is being plugin-ified here here

Beautiful

@OrionReed
Copy link
Author

I've added some of the few major issues to the extension repo and if anyone can fix any of them I will buy you a coffee or drink of your choice!

Specifically, there are 3 issues that are causing the majority of problems:

@OrionReed
Copy link
Author

Revised the gist, should now work on many more websites which previously looked flat thanks to forcing isolation: auto

@Tristan-Trainual
Copy link

This is amazing!

@ichux
Copy link

ichux commented Aug 6, 2024

FYI you can make a bookmarklet in Firefox/Chrome and add this as the url to create a 1-click way of loading this into any site.

javascript:(()=>{function e(){return`hsl(${Math.floor(360*Math.random())}, ${50+Math.floor(30*Math.random())}%, ${40+Math.floor(30*Math.random())}%)`}let t=e=>[...e.children].reduce((e,n)=>Math.max(e,t(n)),0)+1,n=t(document.body),r=(e,t=0,r=0)=>`hsl(${t}, 75%, ${Math.min(10+e*(1+60/n),90)+r}%)`,o=document.body;o.style.overflow="visible",o.style.transformStyle="preserve-3d",o.style.perspective=1e4;let i=window.innerWidth/2,l=window.innerHeight/2;function s(e,t){}function f(e,t,n,o){for(let i=e.childNodes,l=i.length,$=0;$<l;$++){let a=i[$];if(!(1===a.nodeType&&!a.classList.contains("dom-3d-side-face")))continue;let d=r(t,190,-5);Object.assign(a.style,{transform:"translateZ(20px)",overflow:"visible",backfaceVisibility:"hidden",transformStyle:"preserve-3d",backgroundColor:d,willChange:"transform"});let c=n,m=o;a.offsetParent===e&&(c+=e.offsetLeft,m+=e.offsetTop),s(a,d),f(a,t+1,c,m)}}o.style.perspectiveOrigin=o.style.transformOrigin=`${i}px ${l}px`,f(o,0,0,0),document.addEventListener("mousemove",e=>{let t=180*(1-e.clientY/window.innerHeight)-90,n=180*e.clientX/window.innerWidth-90;o.style.transform=`rotateX(${t}deg) rotateY(${n}deg)`})})();

This is excellent. Waoh! Well done

@geode-main
Copy link

FYI you can make a bookmarklet in Firefox/Chrome and add this as the url to create a 1-click way of loading this into any site.

javascript:(()=>{function e(){return`hsl(${Math.floor(360*Math.random())}, ${50+Math.floor(30*Math.random())}%, ${40+Math.floor(30*Math.random())}%)`}let t=e=>[...e.children].reduce((e,n)=>Math.max(e,t(n)),0)+1,n=t(document.body),r=(e,t=0,r=0)=>`hsl(${t}, 75%, ${Math.min(10+e*(1+60/n),90)+r}%)`,o=document.body;o.style.overflow="visible",o.style.transformStyle="preserve-3d",o.style.perspective=1e4;let i=window.innerWidth/2,l=window.innerHeight/2;function s(e,t){}function f(e,t,n,o){for(let i=e.childNodes,l=i.length,$=0;$<l;$++){let a=i[$];if(!(1===a.nodeType&&!a.classList.contains("dom-3d-side-face")))continue;let d=r(t,190,-5);Object.assign(a.style,{transform:"translateZ(20px)",overflow:"visible",backfaceVisibility:"hidden",transformStyle:"preserve-3d",backgroundColor:d,willChange:"transform"});let c=n,m=o;a.offsetParent===e&&(c+=e.offsetLeft,m+=e.offsetTop),s(a,d),f(a,t+1,c,m)}}o.style.perspectiveOrigin=o.style.transformOrigin=`${i}px ${l}px`,f(o,0,0,0),document.addEventListener("mousemove",e=>{let t=180*(1-e.clientY/window.innerHeight)-90,n=180*e.clientX/window.innerWidth-90;o.style.transform=`rotateX(${t}deg) rotateY(${n}deg)`})})();

This is great! Thanks!

@ThatOneCalculator
Copy link

ThatOneCalculator commented Aug 7, 2024

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

@dmarx
Copy link

dmarx commented Aug 7, 2024

Very cool! Just commenting to add a link to your tweet that led me here: https://x.com/OrionReedOne/status/1772934478421188620

@AshokTak
Copy link

AshokTak commented Aug 8, 2024

It's cool! Love it <3

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment