Hi everyone, I’m a beginner programing still learning I create this shadcn style scrollbar, but the only problem is the mouse wheel support I don’t find a way to make it work I attach the code if someone wants to explore into the code.
:: | Scrollbar Code | ::
async function createDialog() {
// Add body cursor style to prevent Photoshop interaction
document.body.style.cursor = "default";
const dlg = document.createElement("dialog");
dlg.style.display = "flex";
dlg.style.width = "450px";
dlg.style.height = "600px";
dlg.style.backgroundColor = "#0b0b0b";
function createCustomScrollArea(items = []) {
// Create isolation wrapper
const isolationWrapper = document.createElement("div");
isolationWrapper.style.position = "relative";
isolationWrapper.style.width = "260px";
isolationWrapper.style.height = "200px";
isolationWrapper.style.contain = "layout style paint size";
isolationWrapper.style.isolation = "isolate";
isolationWrapper.style.zIndex = "1000";
isolationWrapper.style.transform = "translateZ(0)";
isolationWrapper.style.willChange = "transform";
isolationWrapper.style.pointerEvents = "auto";
isolationWrapper.style.overflow = "hidden";
const container = document.createElement("div");
container.style.position = "relative";
container.style.width = "100%";
container.style.height = "100%";
container.style.border = "1px solid #ccc";
container.style.borderRadius = "8px";
container.style.background = "#fff";
container.style.overflow = "hidden";
container.style.padding = "8px";
container.tabIndex = 0;
container.style.outline = "none";
const scrollTrack = document.createElement("div");
scrollTrack.style.position = "relative";
scrollTrack.style.height = "100%";
scrollTrack.style.overflow = "hidden";
const scrollContent = document.createElement("div");
scrollContent.style.position = "absolute";
scrollContent.style.top = "0";
scrollContent.style.left = "0";
scrollContent.style.right = "0";
scrollContent.style.transition = "top 0.1s ease";
items.forEach((text) => {
const item = document.createElement("div");
item.textContent = text;
item.style.padding = "6px 8px";
item.style.fontSize = "13px";
item.style.borderBottom = "1px solid #eee";
scrollContent.appendChild(item);
});
scrollTrack.appendChild(scrollContent);
container.appendChild(scrollTrack);
const scrollBar = document.createElement("div");
scrollBar.style.position = "absolute";
scrollBar.style.top = "8px";
scrollBar.style.right = "4px";
scrollBar.style.width = "6px";
scrollBar.style.height = "100%";
scrollBar.style.background = "transparent";
const scrollThumb = document.createElement("div");
scrollThumb.style.position = "absolute";
scrollThumb.style.top = "0";
scrollThumb.style.left = "0";
scrollThumb.style.width = "100%";
scrollThumb.style.height = "40px";
scrollThumb.style.background = "#999";
scrollThumb.style.borderRadius = "4px";
scrollThumb.style.opacity = "0";
scrollThumb.style.transition = "opacity 0.2s ease";
scrollThumb.style.cursor = "grab";
scrollBar.appendChild(scrollThumb);
container.appendChild(scrollBar);
// Mouse over/leave handlers with debug logging
container.addEventListener("mouseenter", () => {
console.log("Container mouseenter - showing scrollbar");
scrollThumb.style.opacity = "1";
});
container.addEventListener("mouseleave", () => {
console.log("Container mouseleave - hiding scrollbar");
if (!isDragging) {
scrollThumb.style.opacity = "0";
}
});
let currentScrollTop = 0;
function updateScrollPosition(newScrollTop) {
const contentHeight = scrollContent.scrollHeight;
const visibleHeight = scrollTrack.clientHeight;
const maxScroll = Math.max(0, contentHeight - visibleHeight);
currentScrollTop = Math.max(0, Math.min(newScrollTop, maxScroll));
scrollContent.style.top = `-${currentScrollTop}px`;
if (maxScroll > 0) {
const scrollRatio = currentScrollTop / maxScroll;
const maxThumbTop =
container.clientHeight - scrollThumb.clientHeight - 16;
const thumbTop = scrollRatio * maxThumbTop;
scrollThumb.style.top = `${thumbTop}px`;
}
}
// Drag functionality with debug logging
let isDragging = false;
let startY = 0;
let startTop = 0;
scrollThumb.addEventListener("mousedown", (e) => {
console.log("Thumb mousedown - starting drag");
isDragging = true;
startY = e.clientY;
startTop = Number.parseInt(scrollThumb.style.top || "0");
scrollThumb.style.cursor = "grabbing";
e.preventDefault();
});
document.addEventListener("mousemove", (e) => {
if (!isDragging) return;
console.log("Dragging thumb, clientY:", e.clientY, "startY:", startY);
const deltaY = e.clientY - startY;
let newTop = startTop + deltaY;
const maxThumbTop =
container.clientHeight - scrollThumb.clientHeight - 16;
newTop = Math.max(0, Math.min(newTop, maxThumbTop));
console.log("Setting thumb top to:", newTop, "maxThumbTop:", maxThumbTop);
scrollThumb.style.top = `${newTop}px`;
// Visual debug - change thumb color temporarily
scrollThumb.style.background = "#ff0000";
setTimeout(() => {
scrollThumb.style.background = "#999";
}, 200);
if (maxThumbTop > 0) {
const scrollRatio = newTop / maxThumbTop;
const contentHeight = scrollContent.scrollHeight;
const visibleHeight = scrollTrack.clientHeight;
const maxScroll = Math.max(0, contentHeight - visibleHeight);
currentScrollTop = scrollRatio * maxScroll;
console.log("Setting content scroll to:", currentScrollTop);
scrollContent.style.top = `-${currentScrollTop}px`;
}
});
document.addEventListener("mouseup", () => {
if (isDragging) {
console.log("Thumb mouseup - ending drag");
isDragging = false;
scrollThumb.style.cursor = "grab";
}
});
// Store wheel handler function for later binding
let wheelHandler = null;
let isHovered = false;
container.addEventListener("mouseenter", () => {
isHovered = true;
});
container.addEventListener("mouseleave", () => {
isHovered = false;
});
// Wheel event handler with aggressive blocking
wheelHandler = function(e) {
if (!isHovered) return;
console.log("Wheel event caught:", e.type, "deltaY:", e.deltaY);
// Aggressive event blocking for UXP
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
e.cancelBubble = true;
e.returnValue = false;
// Block on the event object itself
if (e.stopEvent) e.stopEvent();
if (e.halt) e.halt();
let delta = e.deltaY || e.wheelDelta || e.detail || 0;
// Normalize wheel delta
if (e.deltaMode === 1) {
delta *= 16;
} else if (e.deltaMode === 2) {
delta *= scrollTrack.clientHeight;
}
const scrollStep = Math.min(Math.abs(delta), 40);
const scrollDirection = delta > 0 ? 1 : -1;
const newScrollTop = currentScrollTop + scrollStep * scrollDirection;
updateScrollPosition(newScrollTop);
return false;
};
// Store reference to container for later event binding
container._wheelHandler = wheelHandler;
// Keyboard alternative
container.addEventListener("keydown", (e) => {
let scrollDelta = 0;
switch(e.key) {
case "ArrowUp": scrollDelta = -30; break;
case "ArrowDown": scrollDelta = 30; break;
case "PageUp": scrollDelta = -100; break;
case "PageDown": scrollDelta = 100; break;
}
if (scrollDelta !== 0) {
e.preventDefault();
updateScrollPosition(currentScrollTop + scrollDelta);
}
});
// Initialize scroll position and check geometry
setTimeout(() => {
console.log("Initializing scroll area...");
const contentHeight = scrollContent.scrollHeight;
const visibleHeight = scrollTrack.clientHeight;
console.log("Content height:", contentHeight, "Visible height:", visibleHeight);
// Set proper thumb height based on content ratio
if (contentHeight > visibleHeight) {
const scrollRatio = visibleHeight / contentHeight;
const thumbHeight = Math.max(20, scrollRatio * scrollBar.clientHeight);
scrollThumb.style.height = `${thumbHeight}px`;
console.log("Set thumb height to:", thumbHeight);
}
updateScrollPosition(0);
console.log("Scroll area initialized");
}, 100);
isolationWrapper.appendChild(container);
return isolationWrapper;
}
const versions = [
"v1.2.0-beta.32",
"v1.2.0-beta.31",
"v1.2.0-beta.30",
"v1.2.0-beta.29",
"v1.2.0-beta.28",
"v1.2.0-beta.27",
"v1.2.0-beta.26",
"v1.2.0-beta.25",
"v1.2.0-beta.24",
"v1.2.0-beta.23",
"v1.2.0-beta.22",
"v1.2.0-beta.21",
"v1.2.0-beta.20",
"v1.2.0-beta.19",
"v1.2.0-beta.18",
"v1.2.0-beta.17",
"v1.2.0-beta.16",
"v1.2.0-beta.15"
];
const panel = document.createElement("div");
panel.style.display = "flex";
panel.style.padding = "20px";
panel.style.width = "350px";
panel.style.height = "480px";
panel.style.borderRadius = "12px";
panel.style.backgroundColor = "#3d3d3f";
panel.appendChild(createCustomScrollArea(versions));
dlg.appendChild(panel);
document.body.appendChild(dlg);
await dlg.showModal();
// Bind wheel events AFTER dialog is shown and rendered
setTimeout(() => {
const scrollArea = panel.querySelector('div[tabindex="0"]');
const isolationWrapper = panel.querySelector('div > div');
if (scrollArea && scrollArea._wheelHandler && isolationWrapper) {
console.log("Binding wheel events after dialog load...");
// Bind with capture to intercept before other handlers
scrollArea.addEventListener("wheel", scrollArea._wheelHandler, { passive: false, capture: true });
scrollArea.addEventListener("mousewheel", scrollArea._wheelHandler, { passive: false, capture: true });
scrollArea.addEventListener("DOMMouseScroll", scrollArea._wheelHandler, { passive: false, capture: true });
// Block all wheel events on the isolation wrapper
const wrapperBlocker = function(e) {
console.log("Isolation wrapper blocking wheel event");
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
e.cancelBubble = true;
e.returnValue = false;
return false;
};
isolationWrapper.addEventListener("wheel", wrapperBlocker, { passive: false, capture: true });
isolationWrapper.addEventListener("mousewheel", wrapperBlocker, { passive: false, capture: true });
isolationWrapper.addEventListener("DOMMouseScroll", wrapperBlocker, { passive: false, capture: true });
console.log("Wheel events bound successfully with isolation wrapper");
}
}, 500);
}
await createDialog();