View a website in 3D
Copy-paste this into your console to visualise the DOM as a stack of solid blocks. Particularly useful for debugging z-index issues.
;(() => {
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)
}
}
})()
Source: OrionReed