@fadhli_fajarsyah/accessibility-widget
Version:
A simple accessibility widget to adjust font size (A+, A-, Reset)
228 lines (196 loc) • 7.64 kB
text/typescript
export function initAccessibilityWidget(): void {
if (document.getElementById("accessibility-widget-container")) return;
const container = document.createElement("div");
container.id = "accessibility-widget-container";
container.style.position = "fixed";
container.style.zIndex = "9999";
container.style.top = "0";
container.style.left = "0";
container.style.height = "100vh";
container.style.width = "auto";
document.body.appendChild(container);
const shadow = container.attachShadow({ mode: "open" });
const savedTop = localStorage.getItem("accessibility-hamburger-top");
const style = document.createElement("style");
style.textContent = `
.toggle-button {
width: 40px;
height: 40px;
border-radius: 0 8px 8px 0;
background-color: #0000ff;
color: white;
font-size: 20px;
border: none;
cursor: grab;
position: fixed;
left: 0;
z-index: 10000;
transition: top 0.2s;
}
.popup-panel {
position: fixed;
top: 0;
left: 0;
height: 100vh;
width: 50vw;
max-width: 400px;
background: white;
box-shadow: 2px 0 10px rgba(0,0,0,0.3);
padding: 20px;
transform: translateX(-100%);
transition: transform 0.3s ease-in-out;
z-index: 9999;
overflow-y: auto;
}
.popup-panel.open {
transform: translateX(0);
}
.accessibility-controls button,
.button {
padding: 8px 12px;
margin: 6px 4px;
background-color: #0000ff;
color: white;
border: none;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
}
.accessibility-controls button:hover {
background-color: #0000cc;
}
.accessibility-controls {
margin-top: 10px;
}
`;
const toggleBtn = document.createElement("button");
toggleBtn.className = "toggle-button";
toggleBtn.innerHTML = `
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.4575 14.0572V9.02866M12.4575 14.0572H11.0861M12.4575 14.0572L15.2004 20.0001M12.4575 9.02866H11.0861M12.4575 9.02866L13.7381 8.81523C14.7101 8.65322 15.6618 8.38692 16.5768 8.02094L17.4861 7.65723M11.0861 14.0572V9.02866M11.0861 14.0572L7.88608 20.0001M11.0861 9.02866L9.97052 8.84273C8.89172 8.66293 7.84586 8.32283 6.86764 7.83372L6.51465 7.65723" stroke="white" stroke-width="2.28571" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12.7309 4.91429C12.7309 5.41923 12.3216 5.82857 11.8166 5.82857C11.3117 5.82857 10.9023 5.41923 10.9023 4.91429C10.9023 4.40934 11.3117 4 11.8166 4C12.3216 4 12.7309 4.40934 12.7309 4.91429Z" fill="white" stroke="white" stroke-width="2.28571" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
`;
toggleBtn.style.top = savedTop ? `${savedTop}px` : "20px";
const panel = document.createElement("div");
panel.className = "popup-panel";
panel.innerHTML = `
<div class="accessibility-controls" id="controls">
<h2>Accessibility Options</h2>
<div>
<button id="btn-increase">A+</button>
<button id="btn-decrease">A-</button>
</div>
<div>
<button id="btn-underline">Underline Links</button>
</div>
<button id="btn-reset" class="button">Reset</button>
</div>
`;
shadow.appendChild(style);
shadow.appendChild(toggleBtn);
shadow.appendChild(panel);
// Panel Toggle
let isOpen = false;
toggleBtn.addEventListener("click", () => {
isOpen = !isOpen;
panel.classList.toggle("open", isOpen);
if (isOpen) {
toggleBtn.style.top = "20px";
toggleBtn.style.cursor = "default";
} else {
toggleBtn.style.cursor = "grab";
const saved = localStorage.getItem("accessibility-hamburger-top");
toggleBtn.style.top = saved ? `${saved}px` : "20px";
}
});
// Font Size Control
const html = document.documentElement;
const step = 2;
const minSize = 12;
const maxSize = 32;
const defaultSize = 16;
const savedFontSize = localStorage.getItem("accessibility-font-size");
if (savedFontSize) {
const size = parseFloat(savedFontSize);
document.querySelectorAll<HTMLElement>("body, body *").forEach((el) => {
el.style.fontSize = `${size}px`;
});
}
function changeFontSize(direction: "increase" | "decrease") {
document.querySelectorAll<HTMLElement>("body, body *").forEach((el) => {
const currentSize = parseFloat(window.getComputedStyle(el).fontSize);
if (isNaN(currentSize)) return;
let newSize = currentSize;
if (direction === "increase" && currentSize < maxSize) newSize += step;
else if (direction === "decrease" && currentSize > minSize)
newSize -= step;
el.style.fontSize = `${newSize}px`;
localStorage.setItem("accessibility-font-size", `${newSize}`);
});
}
function resetFontSize() {
html.style.fontSize = `${defaultSize}px`;
document.querySelectorAll<HTMLElement>("body, body *").forEach((el) => {
el.style.fontSize = "";
});
}
// Underline Feature
let underlineActive =
localStorage.getItem("accessibility-underline") === "true";
function applyUnderline() {
document.querySelectorAll<HTMLElement>("a, .link, #link").forEach((el) => {
el.style.textDecoration = underlineActive ? "underline" : "none";
});
}
if (underlineActive) applyUnderline();
panel.querySelector("#btn-underline")?.addEventListener("click", () => {
underlineActive = !underlineActive;
localStorage.setItem("accessibility-underline", underlineActive.toString());
applyUnderline();
});
const observer = new MutationObserver(() => {
if (underlineActive) applyUnderline();
});
observer.observe(document.body, { childList: true, subtree: true });
panel
.querySelector("#btn-increase")
?.addEventListener("click", () => changeFontSize("increase"));
panel
.querySelector("#btn-decrease")
?.addEventListener("click", () => changeFontSize("decrease"));
panel.querySelector("#btn-reset")?.addEventListener("click", () => {
resetFontSize();
underlineActive = false;
applyUnderline();
localStorage.removeItem("accessibility-underline");
localStorage.removeItem("accessibility-font-size");
});
// Drag Only When Closed
let isDragging = false;
let startY = 0;
let startTop = 0;
toggleBtn.addEventListener("mousedown", (e) => {
if (isOpen) return;
isDragging = true;
startY = e.clientY;
startTop = toggleBtn.getBoundingClientRect().top;
document.body.style.userSelect = "none";
});
document.addEventListener("mousemove", (e) => {
if (!isDragging || isOpen) return;
const offsetY = e.clientY - startY;
let newTop = startTop + offsetY;
const maxTop = window.innerHeight - 50;
newTop = Math.max(0, Math.min(maxTop, newTop));
toggleBtn.style.top = `${newTop}px`;
});
document.addEventListener("mouseup", () => {
if (isDragging) {
isDragging = false;
document.body.style.userSelect = "";
const rect = toggleBtn.getBoundingClientRect();
localStorage.setItem("accessibility-hamburger-top", `${rect.top}`);
}
});
}