-
-
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 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); | |
} | |
} | |
})() |
As requested, I've made a browser extension. A little development help would be massive — currently only works in Firefox so Chrome/Safari help would be great. Once it's polished, I'll make it freely available in extension stores for easy usage.
I will continue to update this gist with improvements from that repo so that you can always just copy-paste it into your console.
This is very cool, thanks for sharing it. One thing to note is that it breaks inside elements that have the style isolation: isolate;
. I think it's probably best to force isolation: auto;
on all elements when using this.
does anyone want a cursed self minifying header? :D
requires terser
or a clipboard tool that allows stdin
to be piped into it to be within yr path:
#!/bin/zsh
//(){ : ;}
//;set +xe # set to -xe if you need to debug
//;NEW="$(printf $0 | sed 's/\.js//g')".min.js
//;compress_opt(){ terser --compress ecma=5,computed_props=false $@ }
//;clean_opt(){ compress_opt $@ }
//;run(){ clean_opt --keep-classnames --keep-fnames -- $@ }
//;run $0 | grep -v '#!/bin/zsh' > $NEW
//;{ printf 'javascript:' ;cat $NEW ;} | vis-clipboard --copy
//;exit 0
ERRATA: note clean_opt is technically redundant, however it can be used for passing other options to terser
Also there is probably another way to do args passing, however it just works this way :)
cool
so cool
Better to change mousemove
with pointermove
to make it works on touchscreen. Though it will be bit annoying to scroll on mobile phone.
Another way to make it seamless on mobile phone is by handle deviceorientation
event then use: alpha, beta, gamma; value 🤔
Great..! it was cool..
Anyone remember when Firefox had a far better version of this built in? https://www.youtube.com/watch?v=zqHV625EU3E
Anyone remember when Firefox had this built in?
yes
@krzentner could you give an example of the isolation: isolate
issue? Happy to make the change once I see what it's doing.
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.
@ylluminate ^ absolutely. we can push improvement from that repo into this single-function gist and reference those issues/changes here.
This should be a browser plugin.
I have a updated version without the blue color:
https://gist.github.com/swnck/7aa80d2968a115ada8d920d772823cc8
@malikalimoekhamedov it is being plugin-ified here here
@malikalimoekhamedov it is being plugin-ified here here
Beautiful
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:
Revised the gist, should now work on many more websites which previously looked flat thanks to forcing isolation: auto
This is amazing!
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
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!
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
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
This is mad cool