Skip to content

Commit

Permalink
Refactor and simplify (#16)
Browse files Browse the repository at this point in the history
* use .children instead of .childNodes
* add firefox-specific ID
* refactored dom3d() for legibility/maintainability
* removed broken toggle in favour of click-to-enable
* bump version
  • Loading branch information
OrionReed authored Mar 30, 2024
1 parent c9edb5d commit 7cc66c9
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 104 deletions.
6 changes: 6 additions & 0 deletions manifest-firefox.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,11 @@
"background.js"
],
"type": "module"
},
"browser_specific_settings": {
"gecko": {
"id": "[email protected]",
"strict_min_version": "109.0"
}
}
}
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "dom3d",
"version": "0.0.2",
"version": "0.0.3",
"description": "View the DOM in 3D.",
"permissions": [
"activeTab",
Expand Down
7 changes: 0 additions & 7 deletions src/background.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { getBrowser } from "./browserApi.js";
import { dom3d } from "./dom3d.js";
let enabled = false;

// browser extension state
let showSides = false;
Expand Down Expand Up @@ -129,11 +128,6 @@ browser.contextMenus.onClicked.addListener((info, tab) => {

// Handle enabling/disabling the extension
browser.action.onClicked.addListener(async (tab) => {
if (enabled) {
enabled = false;
browser.tabs.reload(tab.id);
return;
}
try {
await browser.scripting.executeScript({
target: {
Expand All @@ -149,7 +143,6 @@ browser.action.onClicked.addListener(async (tab) => {
selectors,
],
});
enabled = true;
} catch (err) {
console.error(`failed to execute script: ${err}`);
}
Expand Down
202 changes: 106 additions & 96 deletions src/dom3d.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ export function dom3d(
REQUIRE_DRAG,
SELECTORS,
) {
const body = document.body;
if (body.classList.contains("dom3d-enabled")) return;
body.classList.add("dom3d-enabled");

const DEFAULT_HUE = 190;
const MAX_ROTATION = 180; // set to 360 to rotate all the way round
const THICKNESS = 20; // thickness of layers
Expand All @@ -22,105 +26,24 @@ export function dom3d(
startRotationX: 0,
startRotationY: 0,
};

const getBodyTransform = () =>
`rotateX(${state.rotationY}deg) rotateY(${state.rotationX}deg) scale(${state.zoomLevel})`;
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;
const html = document.documentElement;
html.style.background = body.style.background;
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);

document.addEventListener(
"pointerdown",
(event) => {
if (!REQUIRE_DRAG || !event.altKey) return;
state.isDragging = true;
state.startX = event.clientX;
state.startY = event.clientY;
state.startRotationX = state.rotationX;
state.startRotationY = state.rotationY;
event.preventDefault(); // Prevent default actions like text selection
},
{ passive: false },
);

document.addEventListener("pointermove", (event) => {
if (REQUIRE_DRAG && !state.isDragging) return;

if (REQUIRE_DRAG && state.isDragging) {
// Drag-based rotation/orbiting
const deltaX = event.clientX - state.startX;
const deltaY = event.clientY - state.startY;

state.rotationX =
state.startRotationX + (MAX_ROTATION * deltaX) / window.innerWidth;
state.rotationY =
state.startRotationY - (MAX_ROTATION * deltaY) / window.innerHeight;
} else {
// Mouse-position-based rotation/orbiting
state.rotationY =
MAX_ROTATION * (1 - event.clientY / window.innerHeight) -
MAX_ROTATION / 2;
state.rotationX =
(MAX_ROTATION * event.clientX) / window.innerWidth - MAX_ROTATION / 2;
}

// Update the DOM transformation
body.style.transform = getBodyTransform();
});

document.addEventListener("pointerup", () => {
state.isDragging = false;
});

// Zoom in/out based on mouse wheel if enabled
document.addEventListener(
"wheel",
(event) => {
if (!ZOOM_ENABLED) return;
event.preventDefault();
state.zoomLevel = Math.max(
0.1,
Math.min(state.zoomLevel + event.deltaY * 0.001, 2),
);
body.style.transform = getBodyTransform();
},
{ passive: false },
);
applyBaseBodyStyles();
addEventListeners();
traverseDOM(document.body, 0, 0, 0);

// Recursive function to traverse child nodes, apply 3D styles, and create side faces
function traverseDOM(parentNode, depthLevel, offsetX, offsetY) {
for (
let children = parentNode.childNodes,
let children = parentNode.children,
childrenCount = children.length,
i = 0;
i < childrenCount;
i++
) {
const node = children[i];
if (
!(1 === node.nodeType && !node.classList.contains("dom-3d-side-face"))
)
continue;
if (node.classList.contains("dom3d-side-face")) continue;

// Set the color based on the selector or default hue
const hueSelector = SELECTORS.find((hue) => node.matches(hue.selector));
Expand Down Expand Up @@ -152,15 +75,6 @@ export function dom3d(
}
}

//! UTILS —————————————————————————————————————————————————————

function getRandomColor() {
const hue = Math.floor(Math.random() * 360);
const saturation = 40 + Math.floor(Math.random() * 30);
const lightness = 30 + Math.floor(Math.random() * 30);
return `hsl(${hue}, ${saturation}%, ${lightness}%)`;
}

// Create side faces for an element to give it a 3D appearance
function createSideFaces(element, color) {
if (!SHOW_SIDES) return;
Expand All @@ -180,7 +94,7 @@ export function dom3d(
bottom = "",
}) => {
const face = document.createElement("div");
face.className = "dom-3d-side-face";
face.className = "dom3d-side-face";
Object.assign(face.style, {
transformStyle: "preserve-3d",
backfaceVisibility: "hidden",
Expand Down Expand Up @@ -241,4 +155,100 @@ export function dom3d(

element.appendChild(fragment);
}

// UTILS —————————————————————————————————————————————————————

function getRandomColor() {
const hue = Math.floor(Math.random() * 360);
const saturation = 40 + Math.floor(Math.random() * 30);
const lightness = 30 + Math.floor(Math.random() * 30);
return `hsl(${hue}, ${saturation}%, ${lightness}%)`;
}

function getBodyTransform() {
return `rotateX(${state.rotationY}deg) rotateY(${state.rotationX}deg) scale(${state.zoomLevel})`;
}
function getDOMDepth(element) {
return (
[...element.children].reduce(
(max, child) => Math.max(max, getDOMDepth(child)),
0,
) + 1
);
}
function getColorByDepth(depth, hue = 0, lighten = 0) {
return `hsl(${hue}, 75%, ${
Math.min(10 + depth * (1 + 60 / domDepthCache), 90) + lighten
}%)`;
}

function applyBaseBodyStyles() {
const html = document.documentElement;
const perspectiveOriginX = window.innerWidth / 2;
const perspectiveOriginY = window.innerHeight / 2;
html.style.background = body.style.background;
body.style.overflow = "visible";
body.style.transformStyle = "preserve-3d";
body.style.perspective = `${PERSPECTIVE}`;
body.style.perspectiveOrigin =
body.style.transformOrigin = `${perspectiveOriginX}px ${perspectiveOriginY}px`;
}

// EVENT LISTENERS ——————————————————————————————————————————

function handlePointerDown(event) {
if (!REQUIRE_DRAG || !event.altKey) return;
state.isDragging = true;
state.startX = event.clientX;
state.startY = event.clientY;
state.startRotationX = state.rotationX;
state.startRotationY = state.rotationY;
event.preventDefault(); // Prevent default actions like text selection
}

function handleWheel(event) {
if (!ZOOM_ENABLED) return;
event.preventDefault();
state.zoomLevel = Math.max(
0.1,
Math.min(state.zoomLevel + event.deltaY * 0.001, 2),
);
body.style.transform = getBodyTransform();
}

function handlePointerMove(event) {
if (REQUIRE_DRAG && !state.isDragging) return;

if (REQUIRE_DRAG && state.isDragging) {
// Drag-based rotation/orbiting
const deltaX = event.clientX - state.startX;
const deltaY = event.clientY - state.startY;

state.rotationX =
state.startRotationX + (MAX_ROTATION * deltaX) / window.innerWidth;
state.rotationY =
state.startRotationY - (MAX_ROTATION * deltaY) / window.innerHeight;
} else {
// Mouse-position-based rotation/orbiting
state.rotationY =
MAX_ROTATION * (1 - event.clientY / window.innerHeight) -
MAX_ROTATION / 2;
state.rotationX =
(MAX_ROTATION * event.clientX) / window.innerWidth - MAX_ROTATION / 2;
}

// Update the DOM transformation
body.style.transform = getBodyTransform();
}

function addEventListeners() {
document.addEventListener("pointermove", handlePointerMove);
document.addEventListener("wheel", handleWheel, { passive: false });
document.addEventListener("pointerup", () => {
state.isDragging = false;
});
document.addEventListener("pointerdown", handlePointerDown, {
passive: false,
});
}
}

0 comments on commit 7cc66c9

Please sign in to comment.