Skip to content

Instantly share code, notes, and snippets.

@ThimoDEV
Forked from OrionReed/dom3d.js
Created August 6, 2024 15:51
Show Gist options
  • Save ThimoDEV/48e02f9f8fa0723b34ad902ec3be5200 to your computer and use it in GitHub Desktop.
Save ThimoDEV/48e02f9f8fa0723b34ad902ec3be5200 to your computer and use it in GitHub Desktop.

Revisions

  1. @OrionReed OrionReed revised this gist Mar 31, 2024. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions dom3d.js
    Original file line number Diff line number Diff line change
    @@ -120,6 +120,7 @@
    transform: `translateZ(${THICKNESS}px)`,
    overflow: "visible",
    backfaceVisibility: "hidden",
    isolation: "auto",
    transformStyle: "preserve-3d",
    backgroundColor: COLOR_SURFACE ? color : getComputedStyle(childNode).backgroundColor,
    willChange: 'transform',
  2. @OrionReed OrionReed renamed this gist Mar 30, 2024. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  3. @OrionReed OrionReed revised this gist Mar 27, 2024. 1 changed file with 4 additions and 4 deletions.
    8 changes: 4 additions & 4 deletions DOM3D.js
    Original file line number Diff line number Diff line change
    @@ -32,14 +32,14 @@
    traverseDOM(body, 0, 0, 0);

    document.addEventListener("mousemove", (event) => {
    const rotationY = (MAX_ROTATION * (1 - event.screenY / screen.height) - (MAX_ROTATION / 2));
    const rotationX = (MAX_ROTATION * event.screenX / screen.width - (MAX_ROTATION / 2));
    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
    if (!SHOW_SIDES) { return }
    const width = element.offsetWidth;
    const height = element.offsetHeight;
    const fragment = document.createDocumentFragment();
    @@ -115,7 +115,7 @@
    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, 190, -5);
    const color = COLOR_RANDOM ? getRandomColor() : getColorByDepth(depthLevel, COLOR_HUE, -5);
    Object.assign(childNode.style, {
    transform: `translateZ(${THICKNESS}px)`,
    overflow: "visible",
  4. @OrionReed OrionReed revised this gist Mar 27, 2024. 1 changed file with 8 additions and 9 deletions.
    17 changes: 8 additions & 9 deletions DOM3D.js
    Original file line number Diff line number Diff line change
    @@ -5,28 +5,27 @@
    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 COLOR_OPACITY = 1;
    const MAX_ROTATION = 180; // set to 360 to rotate all the way round
    const THICKNESS = 30; // thickness of layers
    const PERSPECTIVE = 1000; // ¯\\_(ツ)_/¯
    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, opacity = 1) => `hsla(${hue}, 75%, ${Math.min(10 + depth * (1 + 60 / domDepthCache), 90) + lighten}%,${opacity})`;
    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 = PERSPECTIVE;
    body.style.perspective = DISTANCE;
    const perspectiveOriginX = (window.innerWidth / 2);
    const perspectiveOriginY = (window.innerHeight / 2);
    body.style.perspectiveOrigin = body.style.transformOrigin = `${perspectiveOriginX}px ${perspectiveOriginY}px`;
    @@ -116,7 +115,7 @@
    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, 190, -5, COLOR_OPACITY);
    const color = COLOR_RANDOM ? getRandomColor() : getColorByDepth(depthLevel, 190, -5);
    Object.assign(childNode.style, {
    transform: `translateZ(${THICKNESS}px)`,
    overflow: "visible",
  5. @OrionReed OrionReed revised this gist Mar 27, 2024. 1 changed file with 31 additions and 24 deletions.
    55 changes: 31 additions & 24 deletions DOM3D.js
    Original file line number Diff line number Diff line change
    @@ -1,19 +1,26 @@
    // 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 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, opacity = 1) => `hsla(${hue}, 75%, ${Math.min(10 + depth * (1 + 60 / domDepthCache), 90) + lighten}%,${opacity})`;

    // Config
    const SHOW_SIDES = true; // color sides of DOM elements?
    const COLOR_TOP_SURFACE = true; // color tops of towers?
    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 COLOR_OPACITY = 1;
    const COLOR_OPACITY = 1;
    const MAX_ROTATION = 180; // set to 360 to rotate all the way round
    const DEPTH_INCREMENT = 30; // height/depth of layers
    const THICKNESS = 30; // thickness of layers
    const PERSPECTIVE = 1000; // ¯\\_(ツ)_/¯
    const SIDE_FACE_CLASS = 'side-face'; // we use this to avoid traversing infinitely


    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, opacity = 1) => `hsla(${hue}, 75%, ${Math.min(10 + depth * (1 + 60 / domDepthCache), 90) + lighten}%,${opacity})`;

    // Apply initial styles to the body to enable 3D perspective
    const body = document.body;
    @@ -32,17 +39,16 @@
    });

    // Create side faces for an element to give it a 3D appearance
    function createSideFaces(element, depthLevel) {
    function createSideFaces(element, color) {
    if (!SHOW_SIDES) return
    const width = element.offsetWidth;
    const height = element.offsetHeight;
    const color = getColorByDepth(depthLevel, 190, -5, COLOR_OPACITY);
    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 = SIDE_FACE_CLASS;
    face.className = 'dom-3d-side-face';
    Object.assign(face.style, {
    transformStyle: "preserve-3d",
    backfaceVisibility: 'hidden',
    @@ -65,16 +71,16 @@
    // Top face
    createFace({
    width,
    height: DEPTH_INCREMENT,
    transform: `rotateX(-270deg) translateY(${-DEPTH_INCREMENT}px)`,
    height: THICKNESS,
    transform: `rotateX(-270deg) translateY(${-THICKNESS}px)`,
    transformOrigin: 'top',
    top: '0px',
    left: '0px',
    });

    // Right face
    createFace({
    width: DEPTH_INCREMENT,
    width: THICKNESS,
    height,
    transform: 'rotateY(90deg)',
    transformOrigin: 'left',
    @@ -85,18 +91,18 @@
    // Bottom face
    createFace({
    width,
    height: DEPTH_INCREMENT,
    transform: `rotateX(-90deg) translateY(${DEPTH_INCREMENT}px)`,
    height: THICKNESS,
    transform: `rotateX(-90deg) translateY(${THICKNESS}px)`,
    transformOrigin: 'bottom',
    bottom: '0px',
    left: '0px'
    });

    // Left face
    createFace({
    width: DEPTH_INCREMENT,
    width: THICKNESS,
    height,
    transform: `translateX(${-DEPTH_INCREMENT}px) rotateY(-90deg)`,
    transform: `translateX(${-THICKNESS}px) rotateY(-90deg)`,
    transformOrigin: 'right',
    top: '0px',
    left: '0px'
    @@ -109,13 +115,14 @@
    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(SIDE_FACE_CLASS))) continue;
    if (!(1 === childNode.nodeType && !childNode.classList.contains('dom-3d-side-face'))) continue;
    const color = COLOR_RANDOM ? getRandomColor() : getColorByDepth(depthLevel, 190, -5, COLOR_OPACITY);
    Object.assign(childNode.style, {
    transform: `translateZ(${DEPTH_INCREMENT}px)`,
    transform: `translateZ(${THICKNESS}px)`,
    overflow: "visible",
    backfaceVisibility: "hidden",
    transformStyle: "preserve-3d",
    backgroundColor: COLOR_TOP_SURFACE ? getColorByDepth(depthLevel, COLOR_HUE, 0, COLOR_OPACITY) : 'transparent',
    backgroundColor: COLOR_SURFACE ? color : getComputedStyle(childNode).backgroundColor,
    willChange: 'transform',
    });

    @@ -125,7 +132,7 @@
    updatedOffsetX += parentNode.offsetLeft;
    updatedOffsetY += parentNode.offsetTop;
    }
    createSideFaces(childNode, depthLevel);
    createSideFaces(childNode, color);
    traverseDOM(childNode, depthLevel + 1, updatedOffsetX, updatedOffsetY);
    }
    }
  6. @OrionReed OrionReed revised this gist Mar 27, 2024. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions DOM3D.js
    Original file line number Diff line number Diff line change
    @@ -9,10 +9,10 @@
    const SHOW_SIDES = true; // color sides of DOM elements?
    const COLOR_TOP_SURFACE = true; // color tops of towers?
    const COLOR_HUE = 190; // hue in HSL (https://hslpicker.com)
    const COLOR_OPACITY = 1; // ¯\\_(ツ)_/¯
    const COLOR_OPACITY = 1;
    const MAX_ROTATION = 180; // set to 360 to rotate all the way round
    const DEPTH_INCREMENT = 30; // height/depth of layers
    const PERSPECTIVE = 1000;
    const PERSPECTIVE = 1000; // ¯\\_(ツ)_/¯
    const SIDE_FACE_CLASS = 'side-face'; // we use this to avoid traversing infinitely

    // Apply initial styles to the body to enable 3D perspective
  7. @OrionReed OrionReed revised this gist Mar 27, 2024. 1 changed file with 5 additions and 5 deletions.
    10 changes: 5 additions & 5 deletions DOM3D.js
    Original file line number Diff line number Diff line change
    @@ -6,10 +6,10 @@
    const getColorByDepth = (depth, hue = 0, lighten = 0, opacity = 1) => `hsla(${hue}, 75%, ${Math.min(10 + depth * (1 + 60 / domDepthCache), 90) + lighten}%,${opacity})`;

    // Config
    const COLOR_SURFACES = true; // color tops of towers?
    const COLOR_SIDES = true; // color sides of DOM elements?
    const SHOW_SIDES = true; // color sides of DOM elements?
    const COLOR_TOP_SURFACE = true; // color tops of towers?
    const COLOR_HUE = 190; // hue in HSL (https://hslpicker.com)
    const COLOR_OPACITY = 1;
    const COLOR_OPACITY = 1; // ¯\\_(ツ)_/¯
    const MAX_ROTATION = 180; // set to 360 to rotate all the way round
    const DEPTH_INCREMENT = 30; // height/depth of layers
    const PERSPECTIVE = 1000;
    @@ -25,7 +25,6 @@
    body.style.perspectiveOrigin = body.style.transformOrigin = `${perspectiveOriginX}px ${perspectiveOriginY}px`;
    traverseDOM(body, 0, 0, 0);

    // Event listener to rotate the DOM based on mouse movement
    document.addEventListener("mousemove", (event) => {
    const rotationY = (MAX_ROTATION * (1 - event.screenY / screen.height) - (MAX_ROTATION / 2));
    const rotationX = (MAX_ROTATION * event.screenX / screen.width - (MAX_ROTATION / 2));
    @@ -34,6 +33,7 @@

    // Create side faces for an element to give it a 3D appearance
    function createSideFaces(element, depthLevel) {
    if (!SHOW_SIDES) return
    const width = element.offsetWidth;
    const height = element.offsetHeight;
    const color = getColorByDepth(depthLevel, 190, -5, COLOR_OPACITY);
    @@ -115,7 +115,7 @@
    overflow: "visible",
    backfaceVisibility: "hidden",
    transformStyle: "preserve-3d",
    backgroundColor: COLOR_SURFACES ? getColorByDepth(depthLevel, COLOR_HUE, 0, COLOR_OPACITY) : 'transparent',
    backgroundColor: COLOR_TOP_SURFACE ? getColorByDepth(depthLevel, COLOR_HUE, 0, COLOR_OPACITY) : 'transparent',
    willChange: 'transform',
    });

  8. @OrionReed OrionReed revised this gist Mar 27, 2024. 1 changed file with 6 additions and 4 deletions.
    10 changes: 6 additions & 4 deletions DOM3D.js
    Original file line number Diff line number Diff line change
    @@ -12,7 +12,7 @@
    const COLOR_OPACITY = 1;
    const MAX_ROTATION = 180; // set to 360 to rotate all the way round
    const DEPTH_INCREMENT = 30; // height/depth of layers
    const PERSPECTIVE = 1000;
    const PERSPECTIVE = 1000;
    const SIDE_FACE_CLASS = 'side-face'; // we use this to avoid traversing infinitely

    // Apply initial styles to the body to enable 3D perspective
    @@ -45,6 +45,7 @@
    face.className = SIDE_FACE_CLASS;
    Object.assign(face.style, {
    transformStyle: "preserve-3d",
    backfaceVisibility: 'hidden',
    position: 'absolute',
    width: `${width}px`,
    height: `${height}px`,
    @@ -65,10 +66,10 @@
    createFace({
    width,
    height: DEPTH_INCREMENT,
    transform: 'rotateX(-90deg)',
    transform: `rotateX(-270deg) translateY(${-DEPTH_INCREMENT}px)`,
    transformOrigin: 'top',
    top: '0px',
    left: '0px'
    left: '0px',
    });

    // Right face
    @@ -85,7 +86,7 @@
    createFace({
    width,
    height: DEPTH_INCREMENT,
    transform: 'rotateX(90deg)',
    transform: `rotateX(-90deg) translateY(${DEPTH_INCREMENT}px)`,
    transformOrigin: 'bottom',
    bottom: '0px',
    left: '0px'
    @@ -112,6 +113,7 @@
    Object.assign(childNode.style, {
    transform: `translateZ(${DEPTH_INCREMENT}px)`,
    overflow: "visible",
    backfaceVisibility: "hidden",
    transformStyle: "preserve-3d",
    backgroundColor: COLOR_SURFACES ? getColorByDepth(depthLevel, COLOR_HUE, 0, COLOR_OPACITY) : 'transparent',
    willChange: 'transform',
  9. @OrionReed OrionReed revised this gist Mar 27, 2024. 1 changed file with 5 additions and 6 deletions.
    11 changes: 5 additions & 6 deletions DOM3D.js
    Original file line number Diff line number Diff line change
    @@ -6,24 +6,23 @@
    const getColorByDepth = (depth, hue = 0, lighten = 0, opacity = 1) => `hsla(${hue}, 75%, ${Math.min(10 + depth * (1 + 60 / domDepthCache), 90) + lighten}%,${opacity})`;

    // Config
    const MAX_ROTATION = 180; // set to 360 to rotate all the way round
    const DEPTH_INCREMENT = 30; // height/depth of layers
    const PERSPECTIVE = 1000; // ¯\_(ツ)_/¯
    const SIDE_FACE_CLASS = 'side-face'; // we use this to avoid traversing infinitely
    const COLOR_SURFACES = true; // color tops of towers?
    const COLOR_SIDES = true; // color sides of DOM elements?
    const COLOR_HUE = 190; // hue in HSL (https://hslpicker.com)
    const COLOR_OPACITY = 1;
    const MAX_ROTATION = 180; // set to 360 to rotate all the way round
    const DEPTH_INCREMENT = 30; // height/depth of layers
    const PERSPECTIVE = 1000;
    const SIDE_FACE_CLASS = 'side-face'; // we use this to avoid traversing infinitely

    // Setup body for 3D perspective
    // 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 = PERSPECTIVE;
    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);

    // Event listener to rotate the DOM based on mouse movement
  10. @OrionReed OrionReed revised this gist Mar 27, 2024. 1 changed file with 103 additions and 83 deletions.
    186 changes: 103 additions & 83 deletions DOM3D.js
    Original file line number Diff line number Diff line change
    @@ -1,54 +1,47 @@
    // Copy-paste into your console (or minify and save as a bookmarklet) to see any DOM in an almost-sorta-working 3D stack of DOM elements.
    // The front faces are colored for debugging, if this gist can be fixed, they can return to their normal styling.
    (function () {
    const MAX_ROTATION = 180;
    const DEPTH_INCREMENT = 25;
    const PERSPECTIVE = 1000;
    const SIDE_FACE_CLASS = 'side-face';
    const MAX_DOM_DEPTH = getMaxDepth(document.body);
    // 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 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, opacity = 1) => `hsla(${hue}, 75%, ${Math.min(10 + depth * (1 + 60 / domDepthCache), 90) + lighten}%,${opacity})`;

    // Calculate color based on depth, ensuring lighter colors for deeper elements
    function getColorByDepth(depth) {
    return `hsl(190, 75%, ${Math.min(10 + depth * (1 + 60 / MAX_DOM_DEPTH), 90)}%)`;
    }
    // Config
    const MAX_ROTATION = 180; // set to 360 to rotate all the way round
    const DEPTH_INCREMENT = 30; // height/depth of layers
    const PERSPECTIVE = 1000; // ¯\_(ツ)_/¯
    const SIDE_FACE_CLASS = 'side-face'; // we use this to avoid traversing infinitely
    const COLOR_SURFACES = true; // color tops of towers?
    const COLOR_SIDES = true; // color sides of DOM elements?
    const COLOR_HUE = 190; // hue in HSL (https://hslpicker.com)
    const COLOR_OPACITY = 1;

    // Calculate the maximum depth of the DOM tree
    function getMaxDepth(element) {
    return Array.from(element.children).reduce((max, child) =>
    Math.max(max, getMaxDepth(child)), 0) + 1;
    }
    // Setup body for 3D perspective
    const body = document.body;
    body.style.overflow = "visible";
    body.style.transformStyle = "preserve-3d";
    body.style.perspective = PERSPECTIVE;
    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);

    // Freate side faces for an element to give it a 3D appearance
    // Event listener to rotate the DOM based on mouse movement
    document.addEventListener("mousemove", (event) => {
    const rotationY = (MAX_ROTATION * (1 - event.screenY / screen.height) - (MAX_ROTATION / 2));
    const rotationX = (MAX_ROTATION * event.screenX / screen.width - (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, depthLevel) {
    const width = element.offsetWidth;
    const height = element.offsetHeight;
    const color = getColorByDepth(depthLevel);

    const faces = [
    {
    transform: `translateX(${width}px) rotateY(90deg)`,
    width: DEPTH_INCREMENT,
    height: height
    },
    {
    transform: `translateX(-${DEPTH_INCREMENT}px) rotateY(-90deg)`,
    width: DEPTH_INCREMENT,
    height: height
    },
    {
    transform: `translateY(-${DEPTH_INCREMENT}px) rotateX(90deg)`,
    width: width,
    height: DEPTH_INCREMENT
    },
    {
    transform: `translateY(${height}px) rotateX(-90deg)`,
    width: width,
    height: DEPTH_INCREMENT
    }
    ];
    const color = getColorByDepth(depthLevel, 190, -5, COLOR_OPACITY);
    const fragment = document.createDocumentFragment();

    // Create a face for each side of the element
    faces.forEach(({ transform, width, height }) => {
    // 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 = SIDE_FACE_CLASS;
    Object.assign(face.style, {
    @@ -57,55 +50,82 @@
    width: `${width}px`,
    height: `${height}px`,
    background: color,
    transform: transform,
    transform,
    transformOrigin,
    overflow: 'hidden',
    backfaceVisibility: 'hidden',
    willChange: 'transform',
    zIndex: `${depthLevel}`
    })
    element.appendChild(face);
    top,
    left,
    right,
    bottom
    });
    fragment.appendChild(face);
    }

    // Top face
    createFace({
    width,
    height: DEPTH_INCREMENT,
    transform: 'rotateX(-90deg)',
    transformOrigin: 'top',
    top: '0px',
    left: '0px'
    });

    // Right face
    createFace({
    width: DEPTH_INCREMENT,
    height,
    transform: 'rotateY(90deg)',
    transformOrigin: 'left',
    top: '0px',
    left: `${width}px`
    });

    // Bottom face
    createFace({
    width,
    height: DEPTH_INCREMENT,
    transform: 'rotateX(90deg)',
    transformOrigin: 'bottom',
    bottom: '0px',
    left: '0px'
    });

    // Left face
    createFace({
    width: DEPTH_INCREMENT,
    height,
    transform: `translateX(${-DEPTH_INCREMENT}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 traverseChildren(parentNode, depthLevel, offsetX, offsetY) {
    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(SIDE_FACE_CLASS)) {
    Object.assign(childNode.style, {
    transform: `translateZ(${DEPTH_INCREMENT}px)`,
    position: 'relative',
    backfaceVisibility: 'hidden',
    overflow: "visible",
    transformStyle: "preserve-3d",
    zIndex: `${depthLevel}`,
    backgroundColor: `#${Math.floor(Math.random() * 16777215).toString(16)}`, // Random color face while debugging
    willChange: 'transform',
    });

    if (!(1 === childNode.nodeType && !childNode.classList.contains(SIDE_FACE_CLASS))) continue;
    Object.assign(childNode.style, {
    transform: `translateZ(${DEPTH_INCREMENT}px)`,
    overflow: "visible",
    transformStyle: "preserve-3d",
    backgroundColor: COLOR_SURFACES ? getColorByDepth(depthLevel, COLOR_HUE, 0, COLOR_OPACITY) : 'transparent',
    willChange: 'transform',
    });

    childNode.offsetParent === parentNode && (offsetX += parentNode.offsetLeft, offsetY += parentNode.offsetTop);
    createSideFaces(childNode, depthLevel);
    traverseChildren(childNode, depthLevel + 1, offsetX, offsetY);
    let updatedOffsetX = offsetX;
    let updatedOffsetY = offsetY;
    if (childNode.offsetParent === parentNode) {
    updatedOffsetX += parentNode.offsetLeft;
    updatedOffsetY += parentNode.offsetTop;
    }
    createSideFaces(childNode, depthLevel);
    traverseDOM(childNode, depthLevel + 1, updatedOffsetX, updatedOffsetY);
    }
    }

    // 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.backfaceVisibility = 'hidden';
    body.style.perspective = PERSPECTIVE;
    const perspectiveOriginX = (window.innerWidth / 2);
    const perspectiveOriginY = (window.innerHeight / 2);
    body.style.perspectiveOrigin = body.style.transformOrigin = `${perspectiveOriginX}px ${perspectiveOriginY}px`;
    traverseChildren(body, 0, 0, 0);

    // Event listener to rotate the DOM based on mouse movement
    document.addEventListener("mousemove", (event) => {
    const rotationY = (MAX_ROTATION * (1 - event.screenY / screen.height) - (MAX_ROTATION / 2));
    const rotationX = (MAX_ROTATION * event.screenX / screen.width - (MAX_ROTATION / 2));
    body.style.transform = `rotateX(${rotationY}deg) rotateY(${rotationX}deg)`;
    });
    })()
  11. @OrionReed OrionReed revised this gist Mar 27, 2024. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions DOM3D.js
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,5 @@
    // Copy-paste into your console (or minify and save as a bookmarklet) to see any DOM in an almost-sorta-working 3D stack of DOM elements.
    // The front faces are colored for debugging, if this gist can be fixed, they can return to their normal styling.
    (function () {
    const MAX_ROTATION = 180;
    const DEPTH_INCREMENT = 25;
  12. @OrionReed OrionReed created this gist Mar 27, 2024.
    109 changes: 109 additions & 0 deletions DOM3D.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,109 @@
    (function () {
    const MAX_ROTATION = 180;
    const DEPTH_INCREMENT = 25;
    const PERSPECTIVE = 1000;
    const SIDE_FACE_CLASS = 'side-face';
    const MAX_DOM_DEPTH = getMaxDepth(document.body);

    // Calculate color based on depth, ensuring lighter colors for deeper elements
    function getColorByDepth(depth) {
    return `hsl(190, 75%, ${Math.min(10 + depth * (1 + 60 / MAX_DOM_DEPTH), 90)}%)`;
    }

    // Calculate the maximum depth of the DOM tree
    function getMaxDepth(element) {
    return Array.from(element.children).reduce((max, child) =>
    Math.max(max, getMaxDepth(child)), 0) + 1;
    }

    // Freate side faces for an element to give it a 3D appearance
    function createSideFaces(element, depthLevel) {
    const width = element.offsetWidth;
    const height = element.offsetHeight;
    const color = getColorByDepth(depthLevel);

    const faces = [
    {
    transform: `translateX(${width}px) rotateY(90deg)`,
    width: DEPTH_INCREMENT,
    height: height
    },
    {
    transform: `translateX(-${DEPTH_INCREMENT}px) rotateY(-90deg)`,
    width: DEPTH_INCREMENT,
    height: height
    },
    {
    transform: `translateY(-${DEPTH_INCREMENT}px) rotateX(90deg)`,
    width: width,
    height: DEPTH_INCREMENT
    },
    {
    transform: `translateY(${height}px) rotateX(-90deg)`,
    width: width,
    height: DEPTH_INCREMENT
    }
    ];

    // Create a face for each side of the element
    faces.forEach(({ transform, width, height }) => {
    const face = document.createElement('div');
    face.className = SIDE_FACE_CLASS;
    Object.assign(face.style, {
    transformStyle: "preserve-3d",
    position: 'absolute',
    width: `${width}px`,
    height: `${height}px`,
    background: color,
    transform: transform,
    overflow: 'hidden',
    backfaceVisibility: 'hidden',
    willChange: 'transform',
    zIndex: `${depthLevel}`
    })
    element.appendChild(face);
    });
    }

    // Recursive function to traverse child nodes, apply 3D styles, and create side faces
    function traverseChildren(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(SIDE_FACE_CLASS)) {
    Object.assign(childNode.style, {
    transform: `translateZ(${DEPTH_INCREMENT}px)`,
    position: 'relative',
    backfaceVisibility: 'hidden',
    overflow: "visible",
    transformStyle: "preserve-3d",
    zIndex: `${depthLevel}`,
    backgroundColor: `#${Math.floor(Math.random() * 16777215).toString(16)}`, // Random color face while debugging
    willChange: 'transform',
    });


    childNode.offsetParent === parentNode && (offsetX += parentNode.offsetLeft, offsetY += parentNode.offsetTop);
    createSideFaces(childNode, depthLevel);
    traverseChildren(childNode, depthLevel + 1, offsetX, offsetY);
    }
    }
    }

    // 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.backfaceVisibility = 'hidden';
    body.style.perspective = PERSPECTIVE;
    const perspectiveOriginX = (window.innerWidth / 2);
    const perspectiveOriginY = (window.innerHeight / 2);
    body.style.perspectiveOrigin = body.style.transformOrigin = `${perspectiveOriginX}px ${perspectiveOriginY}px`;
    traverseChildren(body, 0, 0, 0);

    // Event listener to rotate the DOM based on mouse movement
    document.addEventListener("mousemove", (event) => {
    const rotationY = (MAX_ROTATION * (1 - event.screenY / screen.height) - (MAX_ROTATION / 2));
    const rotationX = (MAX_ROTATION * event.screenX / screen.width - (MAX_ROTATION / 2));
    body.style.transform = `rotateX(${rotationY}deg) rotateY(${rotationX}deg)`;
    });
    })()