louper 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auto.global.js +25 -0
- package/dist/auto.global.js.map +1 -0
- package/dist/index.cjs +239 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +22 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +213 -0
- package/dist/index.js.map +1 -0
- package/package.json +54 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";var Louper=(()=>{var _={zoomLevel:2,radius:150,borderWidth:3,borderColor:"white",hotkey:"Alt"},D=1,F=10,K=.002,I=100;function q(f={}){let t={..._,...f},l=document.createElement("louper-lens"),c=l.attachShadow({mode:"closed"}),u=document.createElement("style");u.textContent=g(),c.appendChild(u);let p=document.createElement("div");p.className="louper-clone-wrapper";let n=document.createElement("div");n.className="louper-clone-container",p.appendChild(n),c.appendChild(p),document.body.appendChild(l);let r=!1,y=0,L=0,b=0,E=0,a=t.zoomLevel,h=null,x=!1,d=null;function g(){let e=t.radius*2;return`
|
|
2
|
+
:host {
|
|
3
|
+
position: fixed; top: 0; left: 0;
|
|
4
|
+
z-index: 2147483647;
|
|
5
|
+
pointer-events: none;
|
|
6
|
+
width: ${e}px; height: ${e}px;
|
|
7
|
+
border-radius: 50%;
|
|
8
|
+
border: ${t.borderWidth}px solid ${t.borderColor};
|
|
9
|
+
box-shadow: 0 0 0 1px rgba(0,0,0,0.1);
|
|
10
|
+
overflow: hidden; opacity: 0;
|
|
11
|
+
transition: opacity ${I}ms ease-out;
|
|
12
|
+
will-change: transform, opacity;
|
|
13
|
+
}
|
|
14
|
+
:host(.louper-active) { opacity: 1; transition: none; }
|
|
15
|
+
.louper-clone-wrapper {
|
|
16
|
+
position: absolute; top: 0; left: 0;
|
|
17
|
+
width: 100%; height: 100%;
|
|
18
|
+
overflow: hidden; border-radius: 50%;
|
|
19
|
+
}
|
|
20
|
+
.louper-clone-container {
|
|
21
|
+
position: absolute; top: 0; left: 0;
|
|
22
|
+
transform-origin: 0 0;
|
|
23
|
+
}
|
|
24
|
+
`}function Y(){x=!1,h=null;let{radius:e,borderWidth:i}=t;l.style.transform=`translate(${y-e-i}px,${L-e-i}px)`;let M=e-(y+b)*a,T=e-(L+E)*a;n.style.transform=`scale(${a}) translate(${M/a}px,${T/a}px)`}function m(){x||(x=!0,h=requestAnimationFrame(Y))}function k(){let e=document.documentElement,i=e.cloneNode(!0);i.querySelectorAll("script").forEach(o=>o.remove()),i.querySelectorAll("louper-lens").forEach(o=>o.remove()),c.querySelectorAll("[data-louper-sheet]").forEach(o=>o.remove());for(let o of Array.from(document.styleSheets))try{if(o.href){let s=document.createElement("link");s.rel="stylesheet",s.href=o.href,s.setAttribute("data-louper-sheet",""),c.appendChild(s)}else if(o.ownerNode instanceof HTMLStyleElement){let s=document.createElement("style");s.textContent=o.ownerNode.textContent,s.setAttribute("data-louper-sheet",""),c.appendChild(s)}}catch{}let M=e.querySelectorAll("canvas"),T=i.querySelectorAll("canvas");M.forEach((o,s)=>{let w=T[s];if(!w)return;w.width=o.width,w.height=o.height;let W=w.getContext("2d");if(W)try{W.drawImage(o,0,0)}catch{}}),i.style.margin="0",i.style.position="absolute",i.style.top="0",i.style.left="0",i.style.width=e.scrollWidth+"px",i.style.height=e.scrollHeight+"px",n.innerHTML="",n.appendChild(i)}let C=new MutationObserver(()=>{r&&(d&&clearTimeout(d),d=setTimeout(()=>{r&&k()},16))});function O(){r||(r=!0,b=window.scrollX,E=window.scrollY,a=t.zoomLevel,k(),l.classList.add("louper-active"),m(),C.observe(document.body,{childList:!0,subtree:!0,characterData:!0,attributes:!0,attributeFilter:["class","style","data-state"]}))}function v(){r&&(r=!1,C.disconnect(),d&&(clearTimeout(d),d=null),l.classList.remove("louper-active"),setTimeout(()=>{r||(n.innerHTML="")},I))}function S(e){e.key===t.hotkey&&!r&&O()}function A(e){e.key===t.hotkey&&v()}function z(e){y=e.clientX,L=e.clientY,r&&m()}function N(){r&&(b=window.scrollX,E=window.scrollY,m())}function $(e){r&&(e.preventDefault(),a=Math.min(F,Math.max(D,a*(1-e.deltaY*K))),m())}t.hotkey&&(window.addEventListener("keydown",S),window.addEventListener("keyup",A),window.addEventListener("blur",v)),window.addEventListener("mousemove",z),window.addEventListener("scroll",N,{passive:!0}),window.addEventListener("wheel",$,{passive:!1});function H(){r=!1,C.disconnect(),d&&clearTimeout(d),h!==null&&cancelAnimationFrame(h),window.removeEventListener("keydown",S),window.removeEventListener("keyup",A),window.removeEventListener("mousemove",z),window.removeEventListener("blur",v),window.removeEventListener("scroll",N),window.removeEventListener("wheel",$),l.remove()}function X(e){t={...t,...e},e.zoomLevel!==void 0&&(a=e.zoomLevel),u.textContent=g(),r&&m()}return{destroy:H,update:X,activate:O,deactivate:v}}function P(){let f=document.currentScript;if(!f)return{};let{zoom:t,radius:l,borderWidth:c,borderColor:u,hotkey:p}=f.dataset,n={};return t&&(n.zoomLevel=Number(t)),l&&(n.radius=Number(l)),c&&(n.borderWidth=Number(c)),u&&(n.borderColor=u),p&&(n.hotkey=p),n}var R=q(P());window.__louper=R;})();
|
|
25
|
+
//# sourceMappingURL=auto.global.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/auto.ts"],"sourcesContent":["export interface LoupeOptions {\n /** Default: 2 */\n zoomLevel?: number\n /** Lens radius in px. Default: 150 */\n radius?: number\n /** Default: 3 */\n borderWidth?: number\n /** Default: \"white\" */\n borderColor?: string\n /** Default: \"Alt\". Set to false to disable. */\n hotkey?: 'Alt' | 'Control' | 'Shift' | 'Meta' | false\n}\n\nexport interface LoupeInstance {\n destroy: () => void\n update: (opts: Partial<LoupeOptions>) => void\n activate: () => void\n deactivate: () => void\n}\n\nexport const DEFAULTS: Required<LoupeOptions> = {\n zoomLevel: 2,\n radius: 150,\n borderWidth: 3,\n borderColor: 'white',\n hotkey: 'Alt',\n}\n\nconst MIN_ZOOM = 1\nconst MAX_ZOOM = 10\nconst ZOOM_SENSITIVITY = 0.002\nconst FADE_OUT = 100\n\nexport function createLouper(userOpts: LoupeOptions = {}): LoupeInstance {\n let opts = { ...DEFAULTS, ...userOpts }\n\n const host = document.createElement('louper-lens')\n const root = host.attachShadow({ mode: 'closed' })\n const styleEl = document.createElement('style')\n styleEl.textContent = css()\n root.appendChild(styleEl)\n const wrapper = document.createElement('div')\n wrapper.className = 'louper-clone-wrapper'\n const content = document.createElement('div')\n content.className = 'louper-clone-container'\n wrapper.appendChild(content)\n root.appendChild(wrapper)\n document.body.appendChild(host)\n\n let active = false\n let mouseX = 0, mouseY = 0, scrollX = 0, scrollY = 0\n let zoomLevel = opts.zoomLevel\n let rafId: number | null = null\n let pending = false\n let mutationTimer: ReturnType<typeof setTimeout> | null = null\n\n function css() {\n const d = opts.radius * 2\n return `\n :host {\n position: fixed; top: 0; left: 0;\n z-index: 2147483647;\n pointer-events: none;\n width: ${d}px; height: ${d}px;\n border-radius: 50%;\n border: ${opts.borderWidth}px solid ${opts.borderColor};\n box-shadow: 0 0 0 1px rgba(0,0,0,0.1);\n overflow: hidden; opacity: 0;\n transition: opacity ${FADE_OUT}ms ease-out;\n will-change: transform, opacity;\n }\n :host(.louper-active) { opacity: 1; transition: none; }\n .louper-clone-wrapper {\n position: absolute; top: 0; left: 0;\n width: 100%; height: 100%;\n overflow: hidden; border-radius: 50%;\n }\n .louper-clone-container {\n position: absolute; top: 0; left: 0;\n transform-origin: 0 0;\n }\n `\n }\n\n function render() {\n pending = false\n rafId = null\n const { radius, borderWidth } = opts\n host.style.transform = `translate(${mouseX - radius - borderWidth}px,${mouseY - radius - borderWidth}px)`\n const cx = radius - (mouseX + scrollX) * zoomLevel\n const cy = radius - (mouseY + scrollY) * zoomLevel\n content.style.transform = `scale(${zoomLevel}) translate(${cx / zoomLevel}px,${cy / zoomLevel}px)`\n }\n\n function scheduleRender() {\n if (!pending) {\n pending = true\n rafId = requestAnimationFrame(render)\n }\n }\n\n function clonePage() {\n const docEl = document.documentElement\n const copy = docEl.cloneNode(true) as HTMLElement\n copy.querySelectorAll('script').forEach(s => s.remove())\n copy.querySelectorAll('louper-lens').forEach(el => el.remove())\n\n root.querySelectorAll('[data-louper-sheet]').forEach(el => el.remove())\n for (const sheet of Array.from(document.styleSheets)) {\n try {\n if (sheet.href) {\n const link = document.createElement('link')\n link.rel = 'stylesheet'\n link.href = sheet.href\n link.setAttribute('data-louper-sheet', '')\n root.appendChild(link)\n } else if (sheet.ownerNode instanceof HTMLStyleElement) {\n const s = document.createElement('style')\n s.textContent = sheet.ownerNode.textContent\n s.setAttribute('data-louper-sheet', '')\n root.appendChild(s)\n }\n } catch {} // cross-origin\n }\n\n const srcCanvases = docEl.querySelectorAll('canvas')\n const dstCanvases = copy.querySelectorAll('canvas')\n srcCanvases.forEach((src, i) => {\n const dst = dstCanvases[i]\n if (!dst) return\n dst.width = src.width\n dst.height = src.height\n const ctx = dst.getContext('2d')\n if (ctx) try { ctx.drawImage(src, 0, 0) } catch {} // tainted\n })\n\n copy.style.margin = '0'\n copy.style.position = 'absolute'\n copy.style.top = '0'\n copy.style.left = '0'\n copy.style.width = docEl.scrollWidth + 'px'\n copy.style.height = docEl.scrollHeight + 'px'\n content.innerHTML = ''\n content.appendChild(copy)\n }\n\n const observer = new MutationObserver(() => {\n if (!active) return\n if (mutationTimer) clearTimeout(mutationTimer)\n mutationTimer = setTimeout(() => { if (active) clonePage() }, 16)\n })\n\n function activate() {\n if (active) return\n active = true\n scrollX = window.scrollX\n scrollY = window.scrollY\n zoomLevel = opts.zoomLevel\n clonePage()\n host.classList.add('louper-active')\n scheduleRender()\n observer.observe(document.body, {\n childList: true, subtree: true, characterData: true,\n attributes: true, attributeFilter: ['class', 'style', 'data-state'],\n })\n }\n\n function deactivate() {\n if (!active) return\n active = false\n observer.disconnect()\n if (mutationTimer) { clearTimeout(mutationTimer); mutationTimer = null }\n host.classList.remove('louper-active')\n setTimeout(() => { if (!active) content.innerHTML = '' }, FADE_OUT)\n }\n\n function onKeyDown(e: KeyboardEvent) { if (e.key === opts.hotkey && !active) activate() }\n function onKeyUp(e: KeyboardEvent) { if (e.key === opts.hotkey) deactivate() }\n function onMouseMove(e: MouseEvent) {\n mouseX = e.clientX\n mouseY = e.clientY\n if (active) scheduleRender()\n }\n function onScroll() {\n if (!active) return\n scrollX = window.scrollX\n scrollY = window.scrollY\n scheduleRender()\n }\n function onWheel(e: WheelEvent) {\n if (!active) return\n e.preventDefault()\n zoomLevel = Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, zoomLevel * (1 - e.deltaY * ZOOM_SENSITIVITY)))\n scheduleRender()\n }\n\n if (opts.hotkey) {\n window.addEventListener('keydown', onKeyDown)\n window.addEventListener('keyup', onKeyUp)\n window.addEventListener('blur', deactivate)\n }\n window.addEventListener('mousemove', onMouseMove)\n window.addEventListener('scroll', onScroll, { passive: true })\n window.addEventListener('wheel', onWheel, { passive: false })\n\n function destroy() {\n active = false\n observer.disconnect()\n if (mutationTimer) clearTimeout(mutationTimer)\n if (rafId !== null) cancelAnimationFrame(rafId)\n window.removeEventListener('keydown', onKeyDown)\n window.removeEventListener('keyup', onKeyUp)\n window.removeEventListener('mousemove', onMouseMove)\n window.removeEventListener('blur', deactivate)\n window.removeEventListener('scroll', onScroll)\n window.removeEventListener('wheel', onWheel)\n host.remove()\n }\n\n function update(newOpts: Partial<LoupeOptions>) {\n opts = { ...opts, ...newOpts }\n if (newOpts.zoomLevel !== undefined) zoomLevel = newOpts.zoomLevel\n styleEl.textContent = css()\n if (active) scheduleRender()\n }\n\n return { destroy, update, activate, deactivate }\n}\n","import { createLouper, type LoupeOptions } from '.'\n\nfunction getScriptOptions(): LoupeOptions {\n const el = document.currentScript as HTMLScriptElement | null\n if (!el) return {}\n const { zoom, radius, borderWidth, borderColor, hotkey } = el.dataset\n const opts: LoupeOptions = {}\n if (zoom) opts.zoomLevel = Number(zoom)\n if (radius) opts.radius = Number(radius)\n if (borderWidth) opts.borderWidth = Number(borderWidth)\n if (borderColor) opts.borderColor = borderColor\n if (hotkey) opts.hotkey = hotkey as LoupeOptions['hotkey']\n return opts\n}\n\nconst instance = createLouper(getScriptOptions())\n;(window as any).__louper = instance\n"],"mappings":"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","names":["DEFAULTS","MIN_ZOOM","MAX_ZOOM","ZOOM_SENSITIVITY","FADE_OUT","createLouper","userOpts","opts","host","root","styleEl","css","wrapper","content","active","mouseX","mouseY","scrollX","scrollY","zoomLevel","rafId","pending","mutationTimer","d","render","radius","borderWidth","cx","cy","scheduleRender","clonePage","docEl","copy","s","el","sheet","link","srcCanvases","dstCanvases","src","i","dst","ctx","observer","activate","deactivate","onKeyDown","onKeyUp","onMouseMove","onScroll","onWheel","destroy","update","newOpts","getScriptOptions","el","zoom","radius","borderWidth","borderColor","hotkey","opts","instance","createLouper"]}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
|
+
DEFAULTS: () => DEFAULTS,
|
|
24
|
+
createLouper: () => createLouper
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(src_exports);
|
|
27
|
+
var DEFAULTS = {
|
|
28
|
+
zoomLevel: 2,
|
|
29
|
+
radius: 150,
|
|
30
|
+
borderWidth: 3,
|
|
31
|
+
borderColor: "white",
|
|
32
|
+
hotkey: "Alt"
|
|
33
|
+
};
|
|
34
|
+
var MIN_ZOOM = 1;
|
|
35
|
+
var MAX_ZOOM = 10;
|
|
36
|
+
var ZOOM_SENSITIVITY = 2e-3;
|
|
37
|
+
var FADE_OUT = 100;
|
|
38
|
+
function createLouper(userOpts = {}) {
|
|
39
|
+
let opts = { ...DEFAULTS, ...userOpts };
|
|
40
|
+
const host = document.createElement("louper-lens");
|
|
41
|
+
const root = host.attachShadow({ mode: "closed" });
|
|
42
|
+
const styleEl = document.createElement("style");
|
|
43
|
+
styleEl.textContent = css();
|
|
44
|
+
root.appendChild(styleEl);
|
|
45
|
+
const wrapper = document.createElement("div");
|
|
46
|
+
wrapper.className = "louper-clone-wrapper";
|
|
47
|
+
const content = document.createElement("div");
|
|
48
|
+
content.className = "louper-clone-container";
|
|
49
|
+
wrapper.appendChild(content);
|
|
50
|
+
root.appendChild(wrapper);
|
|
51
|
+
document.body.appendChild(host);
|
|
52
|
+
let active = false;
|
|
53
|
+
let mouseX = 0, mouseY = 0, scrollX = 0, scrollY = 0;
|
|
54
|
+
let zoomLevel = opts.zoomLevel;
|
|
55
|
+
let rafId = null;
|
|
56
|
+
let pending = false;
|
|
57
|
+
let mutationTimer = null;
|
|
58
|
+
function css() {
|
|
59
|
+
const d = opts.radius * 2;
|
|
60
|
+
return `
|
|
61
|
+
:host {
|
|
62
|
+
position: fixed; top: 0; left: 0;
|
|
63
|
+
z-index: 2147483647;
|
|
64
|
+
pointer-events: none;
|
|
65
|
+
width: ${d}px; height: ${d}px;
|
|
66
|
+
border-radius: 50%;
|
|
67
|
+
border: ${opts.borderWidth}px solid ${opts.borderColor};
|
|
68
|
+
box-shadow: 0 0 0 1px rgba(0,0,0,0.1);
|
|
69
|
+
overflow: hidden; opacity: 0;
|
|
70
|
+
transition: opacity ${FADE_OUT}ms ease-out;
|
|
71
|
+
will-change: transform, opacity;
|
|
72
|
+
}
|
|
73
|
+
:host(.louper-active) { opacity: 1; transition: none; }
|
|
74
|
+
.louper-clone-wrapper {
|
|
75
|
+
position: absolute; top: 0; left: 0;
|
|
76
|
+
width: 100%; height: 100%;
|
|
77
|
+
overflow: hidden; border-radius: 50%;
|
|
78
|
+
}
|
|
79
|
+
.louper-clone-container {
|
|
80
|
+
position: absolute; top: 0; left: 0;
|
|
81
|
+
transform-origin: 0 0;
|
|
82
|
+
}
|
|
83
|
+
`;
|
|
84
|
+
}
|
|
85
|
+
function render() {
|
|
86
|
+
pending = false;
|
|
87
|
+
rafId = null;
|
|
88
|
+
const { radius, borderWidth } = opts;
|
|
89
|
+
host.style.transform = `translate(${mouseX - radius - borderWidth}px,${mouseY - radius - borderWidth}px)`;
|
|
90
|
+
const cx = radius - (mouseX + scrollX) * zoomLevel;
|
|
91
|
+
const cy = radius - (mouseY + scrollY) * zoomLevel;
|
|
92
|
+
content.style.transform = `scale(${zoomLevel}) translate(${cx / zoomLevel}px,${cy / zoomLevel}px)`;
|
|
93
|
+
}
|
|
94
|
+
function scheduleRender() {
|
|
95
|
+
if (!pending) {
|
|
96
|
+
pending = true;
|
|
97
|
+
rafId = requestAnimationFrame(render);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function clonePage() {
|
|
101
|
+
const docEl = document.documentElement;
|
|
102
|
+
const copy = docEl.cloneNode(true);
|
|
103
|
+
copy.querySelectorAll("script").forEach((s) => s.remove());
|
|
104
|
+
copy.querySelectorAll("louper-lens").forEach((el) => el.remove());
|
|
105
|
+
root.querySelectorAll("[data-louper-sheet]").forEach((el) => el.remove());
|
|
106
|
+
for (const sheet of Array.from(document.styleSheets)) {
|
|
107
|
+
try {
|
|
108
|
+
if (sheet.href) {
|
|
109
|
+
const link = document.createElement("link");
|
|
110
|
+
link.rel = "stylesheet";
|
|
111
|
+
link.href = sheet.href;
|
|
112
|
+
link.setAttribute("data-louper-sheet", "");
|
|
113
|
+
root.appendChild(link);
|
|
114
|
+
} else if (sheet.ownerNode instanceof HTMLStyleElement) {
|
|
115
|
+
const s = document.createElement("style");
|
|
116
|
+
s.textContent = sheet.ownerNode.textContent;
|
|
117
|
+
s.setAttribute("data-louper-sheet", "");
|
|
118
|
+
root.appendChild(s);
|
|
119
|
+
}
|
|
120
|
+
} catch {
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const srcCanvases = docEl.querySelectorAll("canvas");
|
|
124
|
+
const dstCanvases = copy.querySelectorAll("canvas");
|
|
125
|
+
srcCanvases.forEach((src, i) => {
|
|
126
|
+
const dst = dstCanvases[i];
|
|
127
|
+
if (!dst) return;
|
|
128
|
+
dst.width = src.width;
|
|
129
|
+
dst.height = src.height;
|
|
130
|
+
const ctx = dst.getContext("2d");
|
|
131
|
+
if (ctx) try {
|
|
132
|
+
ctx.drawImage(src, 0, 0);
|
|
133
|
+
} catch {
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
copy.style.margin = "0";
|
|
137
|
+
copy.style.position = "absolute";
|
|
138
|
+
copy.style.top = "0";
|
|
139
|
+
copy.style.left = "0";
|
|
140
|
+
copy.style.width = docEl.scrollWidth + "px";
|
|
141
|
+
copy.style.height = docEl.scrollHeight + "px";
|
|
142
|
+
content.innerHTML = "";
|
|
143
|
+
content.appendChild(copy);
|
|
144
|
+
}
|
|
145
|
+
const observer = new MutationObserver(() => {
|
|
146
|
+
if (!active) return;
|
|
147
|
+
if (mutationTimer) clearTimeout(mutationTimer);
|
|
148
|
+
mutationTimer = setTimeout(() => {
|
|
149
|
+
if (active) clonePage();
|
|
150
|
+
}, 16);
|
|
151
|
+
});
|
|
152
|
+
function activate() {
|
|
153
|
+
if (active) return;
|
|
154
|
+
active = true;
|
|
155
|
+
scrollX = window.scrollX;
|
|
156
|
+
scrollY = window.scrollY;
|
|
157
|
+
zoomLevel = opts.zoomLevel;
|
|
158
|
+
clonePage();
|
|
159
|
+
host.classList.add("louper-active");
|
|
160
|
+
scheduleRender();
|
|
161
|
+
observer.observe(document.body, {
|
|
162
|
+
childList: true,
|
|
163
|
+
subtree: true,
|
|
164
|
+
characterData: true,
|
|
165
|
+
attributes: true,
|
|
166
|
+
attributeFilter: ["class", "style", "data-state"]
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
function deactivate() {
|
|
170
|
+
if (!active) return;
|
|
171
|
+
active = false;
|
|
172
|
+
observer.disconnect();
|
|
173
|
+
if (mutationTimer) {
|
|
174
|
+
clearTimeout(mutationTimer);
|
|
175
|
+
mutationTimer = null;
|
|
176
|
+
}
|
|
177
|
+
host.classList.remove("louper-active");
|
|
178
|
+
setTimeout(() => {
|
|
179
|
+
if (!active) content.innerHTML = "";
|
|
180
|
+
}, FADE_OUT);
|
|
181
|
+
}
|
|
182
|
+
function onKeyDown(e) {
|
|
183
|
+
if (e.key === opts.hotkey && !active) activate();
|
|
184
|
+
}
|
|
185
|
+
function onKeyUp(e) {
|
|
186
|
+
if (e.key === opts.hotkey) deactivate();
|
|
187
|
+
}
|
|
188
|
+
function onMouseMove(e) {
|
|
189
|
+
mouseX = e.clientX;
|
|
190
|
+
mouseY = e.clientY;
|
|
191
|
+
if (active) scheduleRender();
|
|
192
|
+
}
|
|
193
|
+
function onScroll() {
|
|
194
|
+
if (!active) return;
|
|
195
|
+
scrollX = window.scrollX;
|
|
196
|
+
scrollY = window.scrollY;
|
|
197
|
+
scheduleRender();
|
|
198
|
+
}
|
|
199
|
+
function onWheel(e) {
|
|
200
|
+
if (!active) return;
|
|
201
|
+
e.preventDefault();
|
|
202
|
+
zoomLevel = Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, zoomLevel * (1 - e.deltaY * ZOOM_SENSITIVITY)));
|
|
203
|
+
scheduleRender();
|
|
204
|
+
}
|
|
205
|
+
if (opts.hotkey) {
|
|
206
|
+
window.addEventListener("keydown", onKeyDown);
|
|
207
|
+
window.addEventListener("keyup", onKeyUp);
|
|
208
|
+
window.addEventListener("blur", deactivate);
|
|
209
|
+
}
|
|
210
|
+
window.addEventListener("mousemove", onMouseMove);
|
|
211
|
+
window.addEventListener("scroll", onScroll, { passive: true });
|
|
212
|
+
window.addEventListener("wheel", onWheel, { passive: false });
|
|
213
|
+
function destroy() {
|
|
214
|
+
active = false;
|
|
215
|
+
observer.disconnect();
|
|
216
|
+
if (mutationTimer) clearTimeout(mutationTimer);
|
|
217
|
+
if (rafId !== null) cancelAnimationFrame(rafId);
|
|
218
|
+
window.removeEventListener("keydown", onKeyDown);
|
|
219
|
+
window.removeEventListener("keyup", onKeyUp);
|
|
220
|
+
window.removeEventListener("mousemove", onMouseMove);
|
|
221
|
+
window.removeEventListener("blur", deactivate);
|
|
222
|
+
window.removeEventListener("scroll", onScroll);
|
|
223
|
+
window.removeEventListener("wheel", onWheel);
|
|
224
|
+
host.remove();
|
|
225
|
+
}
|
|
226
|
+
function update(newOpts) {
|
|
227
|
+
opts = { ...opts, ...newOpts };
|
|
228
|
+
if (newOpts.zoomLevel !== void 0) zoomLevel = newOpts.zoomLevel;
|
|
229
|
+
styleEl.textContent = css();
|
|
230
|
+
if (active) scheduleRender();
|
|
231
|
+
}
|
|
232
|
+
return { destroy, update, activate, deactivate };
|
|
233
|
+
}
|
|
234
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
235
|
+
0 && (module.exports = {
|
|
236
|
+
DEFAULTS,
|
|
237
|
+
createLouper
|
|
238
|
+
});
|
|
239
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export interface LoupeOptions {\n /** Default: 2 */\n zoomLevel?: number\n /** Lens radius in px. Default: 150 */\n radius?: number\n /** Default: 3 */\n borderWidth?: number\n /** Default: \"white\" */\n borderColor?: string\n /** Default: \"Alt\". Set to false to disable. */\n hotkey?: 'Alt' | 'Control' | 'Shift' | 'Meta' | false\n}\n\nexport interface LoupeInstance {\n destroy: () => void\n update: (opts: Partial<LoupeOptions>) => void\n activate: () => void\n deactivate: () => void\n}\n\nexport const DEFAULTS: Required<LoupeOptions> = {\n zoomLevel: 2,\n radius: 150,\n borderWidth: 3,\n borderColor: 'white',\n hotkey: 'Alt',\n}\n\nconst MIN_ZOOM = 1\nconst MAX_ZOOM = 10\nconst ZOOM_SENSITIVITY = 0.002\nconst FADE_OUT = 100\n\nexport function createLouper(userOpts: LoupeOptions = {}): LoupeInstance {\n let opts = { ...DEFAULTS, ...userOpts }\n\n const host = document.createElement('louper-lens')\n const root = host.attachShadow({ mode: 'closed' })\n const styleEl = document.createElement('style')\n styleEl.textContent = css()\n root.appendChild(styleEl)\n const wrapper = document.createElement('div')\n wrapper.className = 'louper-clone-wrapper'\n const content = document.createElement('div')\n content.className = 'louper-clone-container'\n wrapper.appendChild(content)\n root.appendChild(wrapper)\n document.body.appendChild(host)\n\n let active = false\n let mouseX = 0, mouseY = 0, scrollX = 0, scrollY = 0\n let zoomLevel = opts.zoomLevel\n let rafId: number | null = null\n let pending = false\n let mutationTimer: ReturnType<typeof setTimeout> | null = null\n\n function css() {\n const d = opts.radius * 2\n return `\n :host {\n position: fixed; top: 0; left: 0;\n z-index: 2147483647;\n pointer-events: none;\n width: ${d}px; height: ${d}px;\n border-radius: 50%;\n border: ${opts.borderWidth}px solid ${opts.borderColor};\n box-shadow: 0 0 0 1px rgba(0,0,0,0.1);\n overflow: hidden; opacity: 0;\n transition: opacity ${FADE_OUT}ms ease-out;\n will-change: transform, opacity;\n }\n :host(.louper-active) { opacity: 1; transition: none; }\n .louper-clone-wrapper {\n position: absolute; top: 0; left: 0;\n width: 100%; height: 100%;\n overflow: hidden; border-radius: 50%;\n }\n .louper-clone-container {\n position: absolute; top: 0; left: 0;\n transform-origin: 0 0;\n }\n `\n }\n\n function render() {\n pending = false\n rafId = null\n const { radius, borderWidth } = opts\n host.style.transform = `translate(${mouseX - radius - borderWidth}px,${mouseY - radius - borderWidth}px)`\n const cx = radius - (mouseX + scrollX) * zoomLevel\n const cy = radius - (mouseY + scrollY) * zoomLevel\n content.style.transform = `scale(${zoomLevel}) translate(${cx / zoomLevel}px,${cy / zoomLevel}px)`\n }\n\n function scheduleRender() {\n if (!pending) {\n pending = true\n rafId = requestAnimationFrame(render)\n }\n }\n\n function clonePage() {\n const docEl = document.documentElement\n const copy = docEl.cloneNode(true) as HTMLElement\n copy.querySelectorAll('script').forEach(s => s.remove())\n copy.querySelectorAll('louper-lens').forEach(el => el.remove())\n\n root.querySelectorAll('[data-louper-sheet]').forEach(el => el.remove())\n for (const sheet of Array.from(document.styleSheets)) {\n try {\n if (sheet.href) {\n const link = document.createElement('link')\n link.rel = 'stylesheet'\n link.href = sheet.href\n link.setAttribute('data-louper-sheet', '')\n root.appendChild(link)\n } else if (sheet.ownerNode instanceof HTMLStyleElement) {\n const s = document.createElement('style')\n s.textContent = sheet.ownerNode.textContent\n s.setAttribute('data-louper-sheet', '')\n root.appendChild(s)\n }\n } catch {} // cross-origin\n }\n\n const srcCanvases = docEl.querySelectorAll('canvas')\n const dstCanvases = copy.querySelectorAll('canvas')\n srcCanvases.forEach((src, i) => {\n const dst = dstCanvases[i]\n if (!dst) return\n dst.width = src.width\n dst.height = src.height\n const ctx = dst.getContext('2d')\n if (ctx) try { ctx.drawImage(src, 0, 0) } catch {} // tainted\n })\n\n copy.style.margin = '0'\n copy.style.position = 'absolute'\n copy.style.top = '0'\n copy.style.left = '0'\n copy.style.width = docEl.scrollWidth + 'px'\n copy.style.height = docEl.scrollHeight + 'px'\n content.innerHTML = ''\n content.appendChild(copy)\n }\n\n const observer = new MutationObserver(() => {\n if (!active) return\n if (mutationTimer) clearTimeout(mutationTimer)\n mutationTimer = setTimeout(() => { if (active) clonePage() }, 16)\n })\n\n function activate() {\n if (active) return\n active = true\n scrollX = window.scrollX\n scrollY = window.scrollY\n zoomLevel = opts.zoomLevel\n clonePage()\n host.classList.add('louper-active')\n scheduleRender()\n observer.observe(document.body, {\n childList: true, subtree: true, characterData: true,\n attributes: true, attributeFilter: ['class', 'style', 'data-state'],\n })\n }\n\n function deactivate() {\n if (!active) return\n active = false\n observer.disconnect()\n if (mutationTimer) { clearTimeout(mutationTimer); mutationTimer = null }\n host.classList.remove('louper-active')\n setTimeout(() => { if (!active) content.innerHTML = '' }, FADE_OUT)\n }\n\n function onKeyDown(e: KeyboardEvent) { if (e.key === opts.hotkey && !active) activate() }\n function onKeyUp(e: KeyboardEvent) { if (e.key === opts.hotkey) deactivate() }\n function onMouseMove(e: MouseEvent) {\n mouseX = e.clientX\n mouseY = e.clientY\n if (active) scheduleRender()\n }\n function onScroll() {\n if (!active) return\n scrollX = window.scrollX\n scrollY = window.scrollY\n scheduleRender()\n }\n function onWheel(e: WheelEvent) {\n if (!active) return\n e.preventDefault()\n zoomLevel = Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, zoomLevel * (1 - e.deltaY * ZOOM_SENSITIVITY)))\n scheduleRender()\n }\n\n if (opts.hotkey) {\n window.addEventListener('keydown', onKeyDown)\n window.addEventListener('keyup', onKeyUp)\n window.addEventListener('blur', deactivate)\n }\n window.addEventListener('mousemove', onMouseMove)\n window.addEventListener('scroll', onScroll, { passive: true })\n window.addEventListener('wheel', onWheel, { passive: false })\n\n function destroy() {\n active = false\n observer.disconnect()\n if (mutationTimer) clearTimeout(mutationTimer)\n if (rafId !== null) cancelAnimationFrame(rafId)\n window.removeEventListener('keydown', onKeyDown)\n window.removeEventListener('keyup', onKeyUp)\n window.removeEventListener('mousemove', onMouseMove)\n window.removeEventListener('blur', deactivate)\n window.removeEventListener('scroll', onScroll)\n window.removeEventListener('wheel', onWheel)\n host.remove()\n }\n\n function update(newOpts: Partial<LoupeOptions>) {\n opts = { ...opts, ...newOpts }\n if (newOpts.zoomLevel !== undefined) zoomLevel = newOpts.zoomLevel\n styleEl.textContent = css()\n if (active) scheduleRender()\n }\n\n return { destroy, update, activate, deactivate }\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
interface LoupeOptions {
|
|
2
|
+
/** Default: 2 */
|
|
3
|
+
zoomLevel?: number;
|
|
4
|
+
/** Lens radius in px. Default: 150 */
|
|
5
|
+
radius?: number;
|
|
6
|
+
/** Default: 3 */
|
|
7
|
+
borderWidth?: number;
|
|
8
|
+
/** Default: "white" */
|
|
9
|
+
borderColor?: string;
|
|
10
|
+
/** Default: "Alt". Set to false to disable. */
|
|
11
|
+
hotkey?: 'Alt' | 'Control' | 'Shift' | 'Meta' | false;
|
|
12
|
+
}
|
|
13
|
+
interface LoupeInstance {
|
|
14
|
+
destroy: () => void;
|
|
15
|
+
update: (opts: Partial<LoupeOptions>) => void;
|
|
16
|
+
activate: () => void;
|
|
17
|
+
deactivate: () => void;
|
|
18
|
+
}
|
|
19
|
+
declare const DEFAULTS: Required<LoupeOptions>;
|
|
20
|
+
declare function createLouper(userOpts?: LoupeOptions): LoupeInstance;
|
|
21
|
+
|
|
22
|
+
export { DEFAULTS, type LoupeInstance, type LoupeOptions, createLouper };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
interface LoupeOptions {
|
|
2
|
+
/** Default: 2 */
|
|
3
|
+
zoomLevel?: number;
|
|
4
|
+
/** Lens radius in px. Default: 150 */
|
|
5
|
+
radius?: number;
|
|
6
|
+
/** Default: 3 */
|
|
7
|
+
borderWidth?: number;
|
|
8
|
+
/** Default: "white" */
|
|
9
|
+
borderColor?: string;
|
|
10
|
+
/** Default: "Alt". Set to false to disable. */
|
|
11
|
+
hotkey?: 'Alt' | 'Control' | 'Shift' | 'Meta' | false;
|
|
12
|
+
}
|
|
13
|
+
interface LoupeInstance {
|
|
14
|
+
destroy: () => void;
|
|
15
|
+
update: (opts: Partial<LoupeOptions>) => void;
|
|
16
|
+
activate: () => void;
|
|
17
|
+
deactivate: () => void;
|
|
18
|
+
}
|
|
19
|
+
declare const DEFAULTS: Required<LoupeOptions>;
|
|
20
|
+
declare function createLouper(userOpts?: LoupeOptions): LoupeInstance;
|
|
21
|
+
|
|
22
|
+
export { DEFAULTS, type LoupeInstance, type LoupeOptions, createLouper };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
var DEFAULTS = {
|
|
3
|
+
zoomLevel: 2,
|
|
4
|
+
radius: 150,
|
|
5
|
+
borderWidth: 3,
|
|
6
|
+
borderColor: "white",
|
|
7
|
+
hotkey: "Alt"
|
|
8
|
+
};
|
|
9
|
+
var MIN_ZOOM = 1;
|
|
10
|
+
var MAX_ZOOM = 10;
|
|
11
|
+
var ZOOM_SENSITIVITY = 2e-3;
|
|
12
|
+
var FADE_OUT = 100;
|
|
13
|
+
function createLouper(userOpts = {}) {
|
|
14
|
+
let opts = { ...DEFAULTS, ...userOpts };
|
|
15
|
+
const host = document.createElement("louper-lens");
|
|
16
|
+
const root = host.attachShadow({ mode: "closed" });
|
|
17
|
+
const styleEl = document.createElement("style");
|
|
18
|
+
styleEl.textContent = css();
|
|
19
|
+
root.appendChild(styleEl);
|
|
20
|
+
const wrapper = document.createElement("div");
|
|
21
|
+
wrapper.className = "louper-clone-wrapper";
|
|
22
|
+
const content = document.createElement("div");
|
|
23
|
+
content.className = "louper-clone-container";
|
|
24
|
+
wrapper.appendChild(content);
|
|
25
|
+
root.appendChild(wrapper);
|
|
26
|
+
document.body.appendChild(host);
|
|
27
|
+
let active = false;
|
|
28
|
+
let mouseX = 0, mouseY = 0, scrollX = 0, scrollY = 0;
|
|
29
|
+
let zoomLevel = opts.zoomLevel;
|
|
30
|
+
let rafId = null;
|
|
31
|
+
let pending = false;
|
|
32
|
+
let mutationTimer = null;
|
|
33
|
+
function css() {
|
|
34
|
+
const d = opts.radius * 2;
|
|
35
|
+
return `
|
|
36
|
+
:host {
|
|
37
|
+
position: fixed; top: 0; left: 0;
|
|
38
|
+
z-index: 2147483647;
|
|
39
|
+
pointer-events: none;
|
|
40
|
+
width: ${d}px; height: ${d}px;
|
|
41
|
+
border-radius: 50%;
|
|
42
|
+
border: ${opts.borderWidth}px solid ${opts.borderColor};
|
|
43
|
+
box-shadow: 0 0 0 1px rgba(0,0,0,0.1);
|
|
44
|
+
overflow: hidden; opacity: 0;
|
|
45
|
+
transition: opacity ${FADE_OUT}ms ease-out;
|
|
46
|
+
will-change: transform, opacity;
|
|
47
|
+
}
|
|
48
|
+
:host(.louper-active) { opacity: 1; transition: none; }
|
|
49
|
+
.louper-clone-wrapper {
|
|
50
|
+
position: absolute; top: 0; left: 0;
|
|
51
|
+
width: 100%; height: 100%;
|
|
52
|
+
overflow: hidden; border-radius: 50%;
|
|
53
|
+
}
|
|
54
|
+
.louper-clone-container {
|
|
55
|
+
position: absolute; top: 0; left: 0;
|
|
56
|
+
transform-origin: 0 0;
|
|
57
|
+
}
|
|
58
|
+
`;
|
|
59
|
+
}
|
|
60
|
+
function render() {
|
|
61
|
+
pending = false;
|
|
62
|
+
rafId = null;
|
|
63
|
+
const { radius, borderWidth } = opts;
|
|
64
|
+
host.style.transform = `translate(${mouseX - radius - borderWidth}px,${mouseY - radius - borderWidth}px)`;
|
|
65
|
+
const cx = radius - (mouseX + scrollX) * zoomLevel;
|
|
66
|
+
const cy = radius - (mouseY + scrollY) * zoomLevel;
|
|
67
|
+
content.style.transform = `scale(${zoomLevel}) translate(${cx / zoomLevel}px,${cy / zoomLevel}px)`;
|
|
68
|
+
}
|
|
69
|
+
function scheduleRender() {
|
|
70
|
+
if (!pending) {
|
|
71
|
+
pending = true;
|
|
72
|
+
rafId = requestAnimationFrame(render);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function clonePage() {
|
|
76
|
+
const docEl = document.documentElement;
|
|
77
|
+
const copy = docEl.cloneNode(true);
|
|
78
|
+
copy.querySelectorAll("script").forEach((s) => s.remove());
|
|
79
|
+
copy.querySelectorAll("louper-lens").forEach((el) => el.remove());
|
|
80
|
+
root.querySelectorAll("[data-louper-sheet]").forEach((el) => el.remove());
|
|
81
|
+
for (const sheet of Array.from(document.styleSheets)) {
|
|
82
|
+
try {
|
|
83
|
+
if (sheet.href) {
|
|
84
|
+
const link = document.createElement("link");
|
|
85
|
+
link.rel = "stylesheet";
|
|
86
|
+
link.href = sheet.href;
|
|
87
|
+
link.setAttribute("data-louper-sheet", "");
|
|
88
|
+
root.appendChild(link);
|
|
89
|
+
} else if (sheet.ownerNode instanceof HTMLStyleElement) {
|
|
90
|
+
const s = document.createElement("style");
|
|
91
|
+
s.textContent = sheet.ownerNode.textContent;
|
|
92
|
+
s.setAttribute("data-louper-sheet", "");
|
|
93
|
+
root.appendChild(s);
|
|
94
|
+
}
|
|
95
|
+
} catch {
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const srcCanvases = docEl.querySelectorAll("canvas");
|
|
99
|
+
const dstCanvases = copy.querySelectorAll("canvas");
|
|
100
|
+
srcCanvases.forEach((src, i) => {
|
|
101
|
+
const dst = dstCanvases[i];
|
|
102
|
+
if (!dst) return;
|
|
103
|
+
dst.width = src.width;
|
|
104
|
+
dst.height = src.height;
|
|
105
|
+
const ctx = dst.getContext("2d");
|
|
106
|
+
if (ctx) try {
|
|
107
|
+
ctx.drawImage(src, 0, 0);
|
|
108
|
+
} catch {
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
copy.style.margin = "0";
|
|
112
|
+
copy.style.position = "absolute";
|
|
113
|
+
copy.style.top = "0";
|
|
114
|
+
copy.style.left = "0";
|
|
115
|
+
copy.style.width = docEl.scrollWidth + "px";
|
|
116
|
+
copy.style.height = docEl.scrollHeight + "px";
|
|
117
|
+
content.innerHTML = "";
|
|
118
|
+
content.appendChild(copy);
|
|
119
|
+
}
|
|
120
|
+
const observer = new MutationObserver(() => {
|
|
121
|
+
if (!active) return;
|
|
122
|
+
if (mutationTimer) clearTimeout(mutationTimer);
|
|
123
|
+
mutationTimer = setTimeout(() => {
|
|
124
|
+
if (active) clonePage();
|
|
125
|
+
}, 16);
|
|
126
|
+
});
|
|
127
|
+
function activate() {
|
|
128
|
+
if (active) return;
|
|
129
|
+
active = true;
|
|
130
|
+
scrollX = window.scrollX;
|
|
131
|
+
scrollY = window.scrollY;
|
|
132
|
+
zoomLevel = opts.zoomLevel;
|
|
133
|
+
clonePage();
|
|
134
|
+
host.classList.add("louper-active");
|
|
135
|
+
scheduleRender();
|
|
136
|
+
observer.observe(document.body, {
|
|
137
|
+
childList: true,
|
|
138
|
+
subtree: true,
|
|
139
|
+
characterData: true,
|
|
140
|
+
attributes: true,
|
|
141
|
+
attributeFilter: ["class", "style", "data-state"]
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
function deactivate() {
|
|
145
|
+
if (!active) return;
|
|
146
|
+
active = false;
|
|
147
|
+
observer.disconnect();
|
|
148
|
+
if (mutationTimer) {
|
|
149
|
+
clearTimeout(mutationTimer);
|
|
150
|
+
mutationTimer = null;
|
|
151
|
+
}
|
|
152
|
+
host.classList.remove("louper-active");
|
|
153
|
+
setTimeout(() => {
|
|
154
|
+
if (!active) content.innerHTML = "";
|
|
155
|
+
}, FADE_OUT);
|
|
156
|
+
}
|
|
157
|
+
function onKeyDown(e) {
|
|
158
|
+
if (e.key === opts.hotkey && !active) activate();
|
|
159
|
+
}
|
|
160
|
+
function onKeyUp(e) {
|
|
161
|
+
if (e.key === opts.hotkey) deactivate();
|
|
162
|
+
}
|
|
163
|
+
function onMouseMove(e) {
|
|
164
|
+
mouseX = e.clientX;
|
|
165
|
+
mouseY = e.clientY;
|
|
166
|
+
if (active) scheduleRender();
|
|
167
|
+
}
|
|
168
|
+
function onScroll() {
|
|
169
|
+
if (!active) return;
|
|
170
|
+
scrollX = window.scrollX;
|
|
171
|
+
scrollY = window.scrollY;
|
|
172
|
+
scheduleRender();
|
|
173
|
+
}
|
|
174
|
+
function onWheel(e) {
|
|
175
|
+
if (!active) return;
|
|
176
|
+
e.preventDefault();
|
|
177
|
+
zoomLevel = Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, zoomLevel * (1 - e.deltaY * ZOOM_SENSITIVITY)));
|
|
178
|
+
scheduleRender();
|
|
179
|
+
}
|
|
180
|
+
if (opts.hotkey) {
|
|
181
|
+
window.addEventListener("keydown", onKeyDown);
|
|
182
|
+
window.addEventListener("keyup", onKeyUp);
|
|
183
|
+
window.addEventListener("blur", deactivate);
|
|
184
|
+
}
|
|
185
|
+
window.addEventListener("mousemove", onMouseMove);
|
|
186
|
+
window.addEventListener("scroll", onScroll, { passive: true });
|
|
187
|
+
window.addEventListener("wheel", onWheel, { passive: false });
|
|
188
|
+
function destroy() {
|
|
189
|
+
active = false;
|
|
190
|
+
observer.disconnect();
|
|
191
|
+
if (mutationTimer) clearTimeout(mutationTimer);
|
|
192
|
+
if (rafId !== null) cancelAnimationFrame(rafId);
|
|
193
|
+
window.removeEventListener("keydown", onKeyDown);
|
|
194
|
+
window.removeEventListener("keyup", onKeyUp);
|
|
195
|
+
window.removeEventListener("mousemove", onMouseMove);
|
|
196
|
+
window.removeEventListener("blur", deactivate);
|
|
197
|
+
window.removeEventListener("scroll", onScroll);
|
|
198
|
+
window.removeEventListener("wheel", onWheel);
|
|
199
|
+
host.remove();
|
|
200
|
+
}
|
|
201
|
+
function update(newOpts) {
|
|
202
|
+
opts = { ...opts, ...newOpts };
|
|
203
|
+
if (newOpts.zoomLevel !== void 0) zoomLevel = newOpts.zoomLevel;
|
|
204
|
+
styleEl.textContent = css();
|
|
205
|
+
if (active) scheduleRender();
|
|
206
|
+
}
|
|
207
|
+
return { destroy, update, activate, deactivate };
|
|
208
|
+
}
|
|
209
|
+
export {
|
|
210
|
+
DEFAULTS,
|
|
211
|
+
createLouper
|
|
212
|
+
};
|
|
213
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export interface LoupeOptions {\n /** Default: 2 */\n zoomLevel?: number\n /** Lens radius in px. Default: 150 */\n radius?: number\n /** Default: 3 */\n borderWidth?: number\n /** Default: \"white\" */\n borderColor?: string\n /** Default: \"Alt\". Set to false to disable. */\n hotkey?: 'Alt' | 'Control' | 'Shift' | 'Meta' | false\n}\n\nexport interface LoupeInstance {\n destroy: () => void\n update: (opts: Partial<LoupeOptions>) => void\n activate: () => void\n deactivate: () => void\n}\n\nexport const DEFAULTS: Required<LoupeOptions> = {\n zoomLevel: 2,\n radius: 150,\n borderWidth: 3,\n borderColor: 'white',\n hotkey: 'Alt',\n}\n\nconst MIN_ZOOM = 1\nconst MAX_ZOOM = 10\nconst ZOOM_SENSITIVITY = 0.002\nconst FADE_OUT = 100\n\nexport function createLouper(userOpts: LoupeOptions = {}): LoupeInstance {\n let opts = { ...DEFAULTS, ...userOpts }\n\n const host = document.createElement('louper-lens')\n const root = host.attachShadow({ mode: 'closed' })\n const styleEl = document.createElement('style')\n styleEl.textContent = css()\n root.appendChild(styleEl)\n const wrapper = document.createElement('div')\n wrapper.className = 'louper-clone-wrapper'\n const content = document.createElement('div')\n content.className = 'louper-clone-container'\n wrapper.appendChild(content)\n root.appendChild(wrapper)\n document.body.appendChild(host)\n\n let active = false\n let mouseX = 0, mouseY = 0, scrollX = 0, scrollY = 0\n let zoomLevel = opts.zoomLevel\n let rafId: number | null = null\n let pending = false\n let mutationTimer: ReturnType<typeof setTimeout> | null = null\n\n function css() {\n const d = opts.radius * 2\n return `\n :host {\n position: fixed; top: 0; left: 0;\n z-index: 2147483647;\n pointer-events: none;\n width: ${d}px; height: ${d}px;\n border-radius: 50%;\n border: ${opts.borderWidth}px solid ${opts.borderColor};\n box-shadow: 0 0 0 1px rgba(0,0,0,0.1);\n overflow: hidden; opacity: 0;\n transition: opacity ${FADE_OUT}ms ease-out;\n will-change: transform, opacity;\n }\n :host(.louper-active) { opacity: 1; transition: none; }\n .louper-clone-wrapper {\n position: absolute; top: 0; left: 0;\n width: 100%; height: 100%;\n overflow: hidden; border-radius: 50%;\n }\n .louper-clone-container {\n position: absolute; top: 0; left: 0;\n transform-origin: 0 0;\n }\n `\n }\n\n function render() {\n pending = false\n rafId = null\n const { radius, borderWidth } = opts\n host.style.transform = `translate(${mouseX - radius - borderWidth}px,${mouseY - radius - borderWidth}px)`\n const cx = radius - (mouseX + scrollX) * zoomLevel\n const cy = radius - (mouseY + scrollY) * zoomLevel\n content.style.transform = `scale(${zoomLevel}) translate(${cx / zoomLevel}px,${cy / zoomLevel}px)`\n }\n\n function scheduleRender() {\n if (!pending) {\n pending = true\n rafId = requestAnimationFrame(render)\n }\n }\n\n function clonePage() {\n const docEl = document.documentElement\n const copy = docEl.cloneNode(true) as HTMLElement\n copy.querySelectorAll('script').forEach(s => s.remove())\n copy.querySelectorAll('louper-lens').forEach(el => el.remove())\n\n root.querySelectorAll('[data-louper-sheet]').forEach(el => el.remove())\n for (const sheet of Array.from(document.styleSheets)) {\n try {\n if (sheet.href) {\n const link = document.createElement('link')\n link.rel = 'stylesheet'\n link.href = sheet.href\n link.setAttribute('data-louper-sheet', '')\n root.appendChild(link)\n } else if (sheet.ownerNode instanceof HTMLStyleElement) {\n const s = document.createElement('style')\n s.textContent = sheet.ownerNode.textContent\n s.setAttribute('data-louper-sheet', '')\n root.appendChild(s)\n }\n } catch {} // cross-origin\n }\n\n const srcCanvases = docEl.querySelectorAll('canvas')\n const dstCanvases = copy.querySelectorAll('canvas')\n srcCanvases.forEach((src, i) => {\n const dst = dstCanvases[i]\n if (!dst) return\n dst.width = src.width\n dst.height = src.height\n const ctx = dst.getContext('2d')\n if (ctx) try { ctx.drawImage(src, 0, 0) } catch {} // tainted\n })\n\n copy.style.margin = '0'\n copy.style.position = 'absolute'\n copy.style.top = '0'\n copy.style.left = '0'\n copy.style.width = docEl.scrollWidth + 'px'\n copy.style.height = docEl.scrollHeight + 'px'\n content.innerHTML = ''\n content.appendChild(copy)\n }\n\n const observer = new MutationObserver(() => {\n if (!active) return\n if (mutationTimer) clearTimeout(mutationTimer)\n mutationTimer = setTimeout(() => { if (active) clonePage() }, 16)\n })\n\n function activate() {\n if (active) return\n active = true\n scrollX = window.scrollX\n scrollY = window.scrollY\n zoomLevel = opts.zoomLevel\n clonePage()\n host.classList.add('louper-active')\n scheduleRender()\n observer.observe(document.body, {\n childList: true, subtree: true, characterData: true,\n attributes: true, attributeFilter: ['class', 'style', 'data-state'],\n })\n }\n\n function deactivate() {\n if (!active) return\n active = false\n observer.disconnect()\n if (mutationTimer) { clearTimeout(mutationTimer); mutationTimer = null }\n host.classList.remove('louper-active')\n setTimeout(() => { if (!active) content.innerHTML = '' }, FADE_OUT)\n }\n\n function onKeyDown(e: KeyboardEvent) { if (e.key === opts.hotkey && !active) activate() }\n function onKeyUp(e: KeyboardEvent) { if (e.key === opts.hotkey) deactivate() }\n function onMouseMove(e: MouseEvent) {\n mouseX = e.clientX\n mouseY = e.clientY\n if (active) scheduleRender()\n }\n function onScroll() {\n if (!active) return\n scrollX = window.scrollX\n scrollY = window.scrollY\n scheduleRender()\n }\n function onWheel(e: WheelEvent) {\n if (!active) return\n e.preventDefault()\n zoomLevel = Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, zoomLevel * (1 - e.deltaY * ZOOM_SENSITIVITY)))\n scheduleRender()\n }\n\n if (opts.hotkey) {\n window.addEventListener('keydown', onKeyDown)\n window.addEventListener('keyup', onKeyUp)\n window.addEventListener('blur', deactivate)\n }\n window.addEventListener('mousemove', onMouseMove)\n window.addEventListener('scroll', onScroll, { passive: true })\n window.addEventListener('wheel', onWheel, { passive: false })\n\n function destroy() {\n active = false\n observer.disconnect()\n if (mutationTimer) clearTimeout(mutationTimer)\n if (rafId !== null) cancelAnimationFrame(rafId)\n window.removeEventListener('keydown', onKeyDown)\n window.removeEventListener('keyup', onKeyUp)\n window.removeEventListener('mousemove', onMouseMove)\n window.removeEventListener('blur', deactivate)\n window.removeEventListener('scroll', onScroll)\n window.removeEventListener('wheel', onWheel)\n host.remove()\n }\n\n function update(newOpts: Partial<LoupeOptions>) {\n opts = { ...opts, ...newOpts }\n if (newOpts.zoomLevel !== undefined) zoomLevel = newOpts.zoomLevel\n styleEl.textContent = css()\n if (active) scheduleRender()\n }\n\n return { destroy, update, activate, deactivate }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "louper",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A lightweight zoom circle dev tool. Hold Alt/Option to magnify any part of your page.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Will Garman",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/willgarman/louper"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"zoom",
|
|
13
|
+
"magnifier",
|
|
14
|
+
"loupe",
|
|
15
|
+
"devtools",
|
|
16
|
+
"inspect",
|
|
17
|
+
"ui"
|
|
18
|
+
],
|
|
19
|
+
"type": "module",
|
|
20
|
+
"main": "./dist/index.cjs",
|
|
21
|
+
"module": "./dist/index.js",
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"import": {
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"default": "./dist/index.js"
|
|
28
|
+
},
|
|
29
|
+
"require": {
|
|
30
|
+
"types": "./dist/index.d.cts",
|
|
31
|
+
"default": "./dist/index.cjs"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"./auto": "./dist/auto.global.js"
|
|
35
|
+
},
|
|
36
|
+
"sideEffects": [
|
|
37
|
+
"./dist/auto.global.js"
|
|
38
|
+
],
|
|
39
|
+
"files": [
|
|
40
|
+
"dist"
|
|
41
|
+
],
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "tsup",
|
|
44
|
+
"dev": "tsup --watch",
|
|
45
|
+
"test": "vitest run",
|
|
46
|
+
"test:watch": "vitest"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"tsup": "^8.0.0",
|
|
50
|
+
"typescript": "^5.4.0",
|
|
51
|
+
"vitest": "^2.0.0",
|
|
52
|
+
"jsdom": "^25.0.0"
|
|
53
|
+
}
|
|
54
|
+
}
|