scratch-reveal 1.0.0-dev.0 → 1.0.0-dev.1
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/index.cjs.js +4 -4
- package/dist/index.es.js +63 -71
- package/dist/index.umd.js +4 -4
- package/dist/options.d.ts +0 -3
- package/dist/scratch-reveal.css +1 -1
- package/package.json +1 -1
package/dist/index.cjs.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});class
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});class z{ctx;mouseX;mouseY;constructor(t,e,s){this.ctx=t,this.mouseX=e,this.mouseY=s}updateMousePosition(t,e){this.mouseX=t,this.mouseY=e}brush(t,e=0){if(!t){const i=new Error("Brush.brush: img is required");console.log(i.message);return}const s=Math.atan2(this.mouseY,this.mouseX);if(this.ctx.save(),this.ctx.translate(this.mouseX,this.mouseY),this.ctx.rotate(s),e>0){const i=e,n=e*(t.height/t.width);this.ctx.drawImage(t,-(i/2),-(n/2),i,n)}else this.ctx.drawImage(t,-(t.width/2),-(t.height/2));this.ctx.restore()}}function b(h){return new Promise((t,e)=>{const s=new Image;s.crossOrigin="anonymous",s.onload=()=>t(s),s.onerror=()=>e(new Error(`Image ${h} failed to load`)),s.src=h})}function P(h){let t=0;return((...s)=>{t||(t=requestAnimationFrame(()=>{t=0,h(...s)}))})}const m=`.sr {
|
|
2
2
|
position: relative;
|
|
3
3
|
overflow: hidden;
|
|
4
4
|
width: 100%;
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|
scratch-reveal {
|
|
24
24
|
display: inline-block;
|
|
25
|
-
width:
|
|
26
|
-
height:
|
|
25
|
+
width: 100%;
|
|
26
|
+
height: 100%;
|
|
27
27
|
}
|
|
28
|
-
`,
|
|
28
|
+
`,A=m;function R(h){return"adoptedStyleSheets"in h}function I(h){if(typeof CSSStyleSheet>"u")return null;try{const t=new CSSStyleSheet;return t.replaceSync(h),t}catch{return null}}const f=I(m),a={width:300,height:300,brushSrc:"/demo/assets/brush.png",imageMaskSrc:"/demo/assets/scratch-reveal.png",imageBackgroundSrc:"/demo/assets/scratch-reveal-background.svg",brushSize:0,percentToFinish:60,enabledPercentUpdate:!0};class M{config;ctx;container;_canvas;brush;maskImage;backgroundImage;brushImage;backgroundEl;brushSize=0;percent=0;done=!1;destroyed=!1;removeListeners;get canvas(){return this._canvas}constructor(t,e={}){if(this.config={...a,...e},this.container=typeof t=="string"?document.querySelector(t):t,!this.container)throw new Error("ScratchReveal: container not found");this._canvas=this.createCanvas(this.config.width,this.config.height),this.ctx=this._canvas.getContext("2d",{willReadFrequently:!0}),this.brush=new z(this.ctx,0,0),this.brushSize=this.config.brushSize,this.container.appendChild(this._canvas)}async init(){const[t,e,s]=await Promise.all([b(this.config.brushSrc),b(this.config.imageMaskSrc),b(this.config.imageBackgroundSrc)]);return this.destroyed?this:(this.brushImage=t,this.maskImage=e,this.backgroundImage=s,this.drawMask(),this.setBackground(),this.bindEvents(),this)}destroy(){this.destroyed=!0,this.removeListeners?.()}getPercent(){return this.percent}createCanvas(t,e){const s=document.createElement("canvas");return s.className="sr__canvas",s.width=t,s.height=e,s.style.width="100%",s.style.height="100%",s}resize(t,e){this.destroyed||this.done||t<=0||e<=0||this._canvas.width===t&&this._canvas.height===e||(this._canvas.width=t,this._canvas.height=e,this.percent=0,this.ctx.globalCompositeOperation="source-over",this.drawMask())}setBrushSize(t){this.destroyed||!Number.isFinite(t)||t<0||(this.brushSize=t)}bindEvents(){const t=P(i=>{this.updatePosition(i),this.scratch(),this.config.enabledPercentUpdate&&(this.percent=this.updatePercent(),this.config.onProgress?.(this.percent)),this.finish(i,t)}),e=i=>{i.preventDefault(),this.updatePosition(i),this.scratch(),this._canvas.setPointerCapture(i.pointerId),this._canvas.addEventListener("pointermove",t),this.config.enabledPercentUpdate&&(this.percent=this.updatePercent(),this.config.onProgress?.(this.percent)),this.finish(i,t)},s=i=>{this.finish(i,t)};this._canvas.addEventListener("pointerdown",e),this._canvas.addEventListener("pointerup",s),this._canvas.addEventListener("pointerleave",s),this.removeListeners=()=>{this._canvas.removeEventListener("pointerdown",e),this._canvas.removeEventListener("pointerup",s),this._canvas.removeEventListener("pointerleave",s),this._canvas.removeEventListener("pointermove",t)}}updatePosition(t){const e=this._canvas.getBoundingClientRect(),s=e.width?this._canvas.width/e.width:1,i=e.height?this._canvas.height/e.height:1,n=(t.clientX-e.left)*s,r=(t.clientY-e.top)*i;this.brush.updateMousePosition(n,r)}drawMask(){this.maskImage&&(this.ctx.globalCompositeOperation="source-over",this.ctx.clearRect(0,0,this._canvas.width,this._canvas.height),this.ctx.drawImage(this.maskImage,0,0,this._canvas.width,this._canvas.height))}setBackground(){if(this.destroyed||!this.backgroundImage||!this.container.contains(this._canvas))return;const t=this.backgroundEl??document.createElement("img");t.src=this.backgroundImage.src,t.className="sr__bg",t.isConnected||this.container.insertBefore(t,this._canvas),this.backgroundEl=t}scratch(){this.brushImage&&(this.ctx.globalCompositeOperation="destination-out",this.ctx.save(),this.brush.brush(this.brushImage,this.brushSize),this.ctx.restore())}updatePercent(){const e=this.ctx.getImageData(0,0,this._canvas.width,this._canvas.height).data;let s=0;for(let i=3;i<e.length;i+=4)e[i]===0&&s++;return s/(this._canvas.width*this._canvas.height)*100}finish(t,e){if(!this.done&&this.percent>this.config.percentToFinish&&(this.done=!0,this.clear(),this._canvas.style.pointerEvents="none",this.config.onComplete?.(),t&&e)){try{this._canvas.releasePointerCapture(t.pointerId)}catch{}this._canvas.removeEventListener("pointermove",e)}}clear(){this.ctx.clearRect(0,0,this._canvas.width,this._canvas.height)}}function S(h="scratch-reveal"){if(typeof window>"u"||!("customElements"in window)||customElements.get(h))return;function t(i,n){if(i===null)return n;const r=i.trim().toLowerCase();return r===""?!0:r==="false"||r==="0"||r==="no"||r==="off"?!1:r==="true"||r==="1"||r==="yes"||r==="on"?!0:n}function e(i,n,r,c){if(!i)return c;const o=i.trim();if(!o)return c;if(o.endsWith("%")){const u=Number.parseFloat(o.slice(0,-1));if(!Number.isFinite(u))return c;const d=Math.min(n,r);return Math.max(0,d*u/100)}const l=o.endsWith("px")?Number.parseFloat(o.slice(0,-2)):Number.parseFloat(o);return Number.isFinite(l)?Math.max(0,l):c}class s extends HTMLElement{instance;container;styleEl=null;rebuildScheduled=!1;resizeObserver;static get observedAttributes(){return["width","height","percent-to-finish","brush-src","brush-size","mask-src","background-src","enabled-percent-update"]}constructor(){super();const n=this.attachShadow({mode:"open"});R(n)&&f?n.adoptedStyleSheets=[f]:(this.styleEl=document.createElement("style"),this.styleEl.textContent=m,n.append(this.styleEl)),this.container=document.createElement("div"),this.container.className="sr",n.append(this.container)}connectedCallback(){this.scheduleRebuild()}disconnectedCallback(){this.instance?.destroy(),this.instance=void 0,this.resizeObserver?.disconnect(),this.resizeObserver=void 0}attributeChangedCallback(n,r,c){r!==c&&this.scheduleRebuild()}scheduleRebuild(){this.rebuildScheduled||(this.rebuildScheduled=!0,queueMicrotask(()=>{this.rebuildScheduled=!1,this.isConnected&&this.rebuild()}))}rebuild(){this.container.replaceChildren(),this.instance?.destroy();const n=this.hasAttribute("width"),r=this.hasAttribute("height"),c=this.getBoundingClientRect(),o=Math.round(c.width),l=Math.round(c.height),u=n?Number(this.getAttribute("width")):o||a.width,d=r?Number(this.getAttribute("height")):l||a.height,w=Number(this.getAttribute("percent-to-finish")??a.percentToFinish),_=this.getAttribute("brush-src")??a.brushSrc,E=t(this.getAttribute("enabled-percent-update"),a.enabledPercentUpdate),y=e(this.getAttribute("brush-size"),u,d,a.brushSize),k=this.getAttribute("mask-src")??a.imageMaskSrc,x=this.getAttribute("background-src")??a.imageBackgroundSrc;n?this.container.style.width=`${u}px`:this.container.style.width="100%",r?this.container.style.height=`${d}px`:this.container.style.height="100%",this.instance=new M(this.container,{width:u,height:d,percentToFinish:w,brushSrc:_,brushSize:y,imageMaskSrc:k,imageBackgroundSrc:x,enabledPercentUpdate:E,onProgress:g=>{this.dispatchEvent(new CustomEvent("progress",{detail:{percent:g}}))},onComplete:()=>{this.dispatchEvent(new CustomEvent("complete",{detail:{percent:100}}))}}),this.instance.init(),(!n||!r)&&"ResizeObserver"in window?(this.resizeObserver?.disconnect(),this.resizeObserver=new ResizeObserver(()=>{if(this.hasAttribute("width")&&this.hasAttribute("height")){this.resizeObserver?.disconnect(),this.resizeObserver=void 0;return}const g=this.getBoundingClientRect(),v=Math.round(g.width),p=Math.round(g.height);this.instance?.resize(v,p);const C=e(this.getAttribute("brush-size"),v,p,a.brushSize);this.instance?.setBrushSize(C)}),this.resizeObserver.observe(this)):(this.resizeObserver?.disconnect(),this.resizeObserver=void 0)}}customElements.define(h,s)}function L(h){S(),h.config.globalProperties.$scratchReveal=!0}exports.installScratchReveal=L;exports.registerScratchRevealElement=S;exports.scratchRevealCssText=A;
|
package/dist/index.es.js
CHANGED
|
@@ -16,35 +16,28 @@ class C {
|
|
|
16
16
|
}
|
|
17
17
|
const s = Math.atan2(this.mouseY, this.mouseX);
|
|
18
18
|
if (this.ctx.save(), this.ctx.translate(this.mouseX, this.mouseY), this.ctx.rotate(s), e > 0) {
|
|
19
|
-
const i = e,
|
|
20
|
-
this.ctx.drawImage(t, -(i / 2), -(
|
|
19
|
+
const i = e, n = e * (t.height / t.width);
|
|
20
|
+
this.ctx.drawImage(t, -(i / 2), -(n / 2), i, n);
|
|
21
21
|
} else
|
|
22
22
|
this.ctx.drawImage(t, -(t.width / 2), -(t.height / 2));
|
|
23
23
|
this.ctx.restore();
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
|
-
function b(
|
|
26
|
+
function b(h) {
|
|
27
27
|
return new Promise((t, e) => {
|
|
28
28
|
const s = new Image();
|
|
29
|
-
s.crossOrigin = "anonymous", s.onload = () => t(s), s.onerror = () => e(new Error(`Image ${
|
|
29
|
+
s.crossOrigin = "anonymous", s.onload = () => t(s), s.onerror = () => e(new Error(`Image ${h} failed to load`)), s.src = h;
|
|
30
30
|
});
|
|
31
31
|
}
|
|
32
|
-
function
|
|
32
|
+
function z(h) {
|
|
33
33
|
let t = 0;
|
|
34
34
|
return ((...s) => {
|
|
35
35
|
t || (t = requestAnimationFrame(() => {
|
|
36
|
-
t = 0,
|
|
36
|
+
t = 0, h(...s);
|
|
37
37
|
}));
|
|
38
38
|
});
|
|
39
39
|
}
|
|
40
|
-
|
|
41
|
-
const t = n.getBoundingClientRect();
|
|
42
|
-
return {
|
|
43
|
-
left: t.left + window.scrollX,
|
|
44
|
-
top: t.top + window.scrollY
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
const v = `.sr {
|
|
40
|
+
const m = `.sr {
|
|
48
41
|
position: relative;
|
|
49
42
|
overflow: hidden;
|
|
50
43
|
width: 100%;
|
|
@@ -68,23 +61,23 @@ const v = `.sr {
|
|
|
68
61
|
|
|
69
62
|
scratch-reveal {
|
|
70
63
|
display: inline-block;
|
|
71
|
-
width:
|
|
72
|
-
height:
|
|
64
|
+
width: 100%;
|
|
65
|
+
height: 100%;
|
|
73
66
|
}
|
|
74
|
-
`,
|
|
75
|
-
function
|
|
76
|
-
return "adoptedStyleSheets" in
|
|
67
|
+
`, L = m;
|
|
68
|
+
function A(h) {
|
|
69
|
+
return "adoptedStyleSheets" in h;
|
|
77
70
|
}
|
|
78
|
-
function
|
|
71
|
+
function P(h) {
|
|
79
72
|
if (typeof CSSStyleSheet > "u") return null;
|
|
80
73
|
try {
|
|
81
74
|
const t = new CSSStyleSheet();
|
|
82
|
-
return t.replaceSync(
|
|
75
|
+
return t.replaceSync(h), t;
|
|
83
76
|
} catch {
|
|
84
77
|
return null;
|
|
85
78
|
}
|
|
86
79
|
}
|
|
87
|
-
const
|
|
80
|
+
const f = P(m), a = {
|
|
88
81
|
width: 300,
|
|
89
82
|
height: 300,
|
|
90
83
|
brushSrc: "/demo/assets/brush.png",
|
|
@@ -94,7 +87,7 @@ const w = I(v), a = {
|
|
|
94
87
|
percentToFinish: 60,
|
|
95
88
|
enabledPercentUpdate: !0
|
|
96
89
|
};
|
|
97
|
-
class
|
|
90
|
+
class I {
|
|
98
91
|
config;
|
|
99
92
|
ctx;
|
|
100
93
|
container;
|
|
@@ -108,7 +101,6 @@ class M {
|
|
|
108
101
|
percent = 0;
|
|
109
102
|
done = !1;
|
|
110
103
|
destroyed = !1;
|
|
111
|
-
zone = { top: 0, left: 0 };
|
|
112
104
|
removeListeners;
|
|
113
105
|
get canvas() {
|
|
114
106
|
return this._canvas;
|
|
@@ -126,7 +118,7 @@ class M {
|
|
|
126
118
|
b(this.config.imageMaskSrc),
|
|
127
119
|
b(this.config.imageBackgroundSrc)
|
|
128
120
|
]);
|
|
129
|
-
return this.destroyed ? this : (this.brushImage = t, this.maskImage = e, this.backgroundImage = s, this.drawMask(), this.setBackground(), this.
|
|
121
|
+
return this.destroyed ? this : (this.brushImage = t, this.maskImage = e, this.backgroundImage = s, this.drawMask(), this.setBackground(), this.bindEvents(), this);
|
|
130
122
|
}
|
|
131
123
|
destroy() {
|
|
132
124
|
this.destroyed = !0, this.removeListeners?.();
|
|
@@ -139,16 +131,16 @@ class M {
|
|
|
139
131
|
return s.className = "sr__canvas", s.width = t, s.height = e, s.style.width = "100%", s.style.height = "100%", s;
|
|
140
132
|
}
|
|
141
133
|
resize(t, e) {
|
|
142
|
-
this.destroyed || this.done || t <= 0 || e <= 0 || this._canvas.width === t && this._canvas.height === e || (this._canvas.width = t, this._canvas.height = e, this.percent = 0, this.ctx.globalCompositeOperation = "source-over", this.drawMask()
|
|
134
|
+
this.destroyed || this.done || t <= 0 || e <= 0 || this._canvas.width === t && this._canvas.height === e || (this._canvas.width = t, this._canvas.height = e, this.percent = 0, this.ctx.globalCompositeOperation = "source-over", this.drawMask());
|
|
143
135
|
}
|
|
144
136
|
setBrushSize(t) {
|
|
145
137
|
this.destroyed || !Number.isFinite(t) || t < 0 || (this.brushSize = t);
|
|
146
138
|
}
|
|
147
139
|
bindEvents() {
|
|
148
|
-
const t =
|
|
140
|
+
const t = z((i) => {
|
|
149
141
|
this.updatePosition(i), this.scratch(), this.config.enabledPercentUpdate && (this.percent = this.updatePercent(), this.config.onProgress?.(this.percent)), this.finish(i, t);
|
|
150
142
|
}), e = (i) => {
|
|
151
|
-
i.preventDefault(), this.
|
|
143
|
+
i.preventDefault(), this.updatePosition(i), this.scratch(), this._canvas.setPointerCapture(i.pointerId), this._canvas.addEventListener("pointermove", t), this.config.enabledPercentUpdate && (this.percent = this.updatePercent(), this.config.onProgress?.(this.percent)), this.finish(i, t);
|
|
152
144
|
}, s = (i) => {
|
|
153
145
|
this.finish(i, t);
|
|
154
146
|
};
|
|
@@ -157,8 +149,8 @@ class M {
|
|
|
157
149
|
};
|
|
158
150
|
}
|
|
159
151
|
updatePosition(t) {
|
|
160
|
-
const e = t.clientX -
|
|
161
|
-
this.brush.updateMousePosition(
|
|
152
|
+
const e = this._canvas.getBoundingClientRect(), s = e.width ? this._canvas.width / e.width : 1, i = e.height ? this._canvas.height / e.height : 1, n = (t.clientX - e.left) * s, r = (t.clientY - e.top) * i;
|
|
153
|
+
this.brush.updateMousePosition(n, r);
|
|
162
154
|
}
|
|
163
155
|
drawMask() {
|
|
164
156
|
this.maskImage && (this.ctx.globalCompositeOperation = "source-over", this.ctx.clearRect(0, 0, this._canvas.width, this._canvas.height), this.ctx.drawImage(this.maskImage, 0, 0, this._canvas.width, this._canvas.height));
|
|
@@ -191,25 +183,25 @@ class M {
|
|
|
191
183
|
this.ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
|
|
192
184
|
}
|
|
193
185
|
}
|
|
194
|
-
function
|
|
195
|
-
if (typeof window > "u" || !("customElements" in window) || customElements.get(
|
|
196
|
-
function t(i,
|
|
197
|
-
if (i === null) return
|
|
198
|
-
const
|
|
199
|
-
return
|
|
186
|
+
function M(h = "scratch-reveal") {
|
|
187
|
+
if (typeof window > "u" || !("customElements" in window) || customElements.get(h)) return;
|
|
188
|
+
function t(i, n) {
|
|
189
|
+
if (i === null) return n;
|
|
190
|
+
const r = i.trim().toLowerCase();
|
|
191
|
+
return r === "" ? !0 : r === "false" || r === "0" || r === "no" || r === "off" ? !1 : r === "true" || r === "1" || r === "yes" || r === "on" ? !0 : n;
|
|
200
192
|
}
|
|
201
|
-
function e(i,
|
|
202
|
-
if (!i) return
|
|
203
|
-
const
|
|
204
|
-
if (!
|
|
205
|
-
if (
|
|
206
|
-
const u = Number.parseFloat(
|
|
207
|
-
if (!Number.isFinite(u)) return
|
|
208
|
-
const d = Math.min(
|
|
193
|
+
function e(i, n, r, c) {
|
|
194
|
+
if (!i) return c;
|
|
195
|
+
const o = i.trim();
|
|
196
|
+
if (!o) return c;
|
|
197
|
+
if (o.endsWith("%")) {
|
|
198
|
+
const u = Number.parseFloat(o.slice(0, -1));
|
|
199
|
+
if (!Number.isFinite(u)) return c;
|
|
200
|
+
const d = Math.min(n, r);
|
|
209
201
|
return Math.max(0, d * u / 100);
|
|
210
202
|
}
|
|
211
|
-
const l =
|
|
212
|
-
return Number.isFinite(l) ? Math.max(0, l) :
|
|
203
|
+
const l = o.endsWith("px") ? Number.parseFloat(o.slice(0, -2)) : Number.parseFloat(o);
|
|
204
|
+
return Number.isFinite(l) ? Math.max(0, l) : c;
|
|
213
205
|
}
|
|
214
206
|
class s extends HTMLElement {
|
|
215
207
|
instance;
|
|
@@ -231,8 +223,8 @@ function R(n = "scratch-reveal") {
|
|
|
231
223
|
}
|
|
232
224
|
constructor() {
|
|
233
225
|
super();
|
|
234
|
-
const
|
|
235
|
-
|
|
226
|
+
const n = this.attachShadow({ mode: "open" });
|
|
227
|
+
A(n) && f ? n.adoptedStyleSheets = [f] : (this.styleEl = document.createElement("style"), this.styleEl.textContent = m, n.append(this.styleEl)), this.container = document.createElement("div"), this.container.className = "sr", n.append(this.container);
|
|
236
228
|
}
|
|
237
229
|
connectedCallback() {
|
|
238
230
|
this.scheduleRebuild();
|
|
@@ -240,8 +232,8 @@ function R(n = "scratch-reveal") {
|
|
|
240
232
|
disconnectedCallback() {
|
|
241
233
|
this.instance?.destroy(), this.instance = void 0, this.resizeObserver?.disconnect(), this.resizeObserver = void 0;
|
|
242
234
|
}
|
|
243
|
-
attributeChangedCallback(
|
|
244
|
-
|
|
235
|
+
attributeChangedCallback(n, r, c) {
|
|
236
|
+
r !== c && this.scheduleRebuild();
|
|
245
237
|
}
|
|
246
238
|
scheduleRebuild() {
|
|
247
239
|
this.rebuildScheduled || (this.rebuildScheduled = !0, queueMicrotask(() => {
|
|
@@ -250,56 +242,56 @@ function R(n = "scratch-reveal") {
|
|
|
250
242
|
}
|
|
251
243
|
rebuild() {
|
|
252
244
|
this.container.replaceChildren(), this.instance?.destroy();
|
|
253
|
-
const
|
|
245
|
+
const n = this.hasAttribute("width"), r = this.hasAttribute("height"), c = this.getBoundingClientRect(), o = Math.round(c.width), l = Math.round(c.height), u = n ? Number(this.getAttribute("width")) : o || a.width, d = r ? Number(this.getAttribute("height")) : l || a.height, w = Number(
|
|
254
246
|
this.getAttribute("percent-to-finish") ?? a.percentToFinish
|
|
255
|
-
),
|
|
247
|
+
), S = this.getAttribute("brush-src") ?? a.brushSrc, _ = t(
|
|
256
248
|
this.getAttribute("enabled-percent-update"),
|
|
257
249
|
a.enabledPercentUpdate
|
|
258
|
-
),
|
|
250
|
+
), E = e(
|
|
259
251
|
this.getAttribute("brush-size"),
|
|
260
252
|
u,
|
|
261
253
|
d,
|
|
262
254
|
a.brushSize
|
|
263
|
-
),
|
|
264
|
-
|
|
255
|
+
), y = this.getAttribute("mask-src") ?? a.imageMaskSrc, k = this.getAttribute("background-src") ?? a.imageBackgroundSrc;
|
|
256
|
+
n ? this.container.style.width = `${u}px` : this.container.style.width = "100%", r ? this.container.style.height = `${d}px` : this.container.style.height = "100%", this.instance = new I(this.container, {
|
|
265
257
|
width: u,
|
|
266
258
|
height: d,
|
|
267
|
-
percentToFinish:
|
|
268
|
-
brushSrc:
|
|
269
|
-
brushSize:
|
|
270
|
-
imageMaskSrc:
|
|
259
|
+
percentToFinish: w,
|
|
260
|
+
brushSrc: S,
|
|
261
|
+
brushSize: E,
|
|
262
|
+
imageMaskSrc: y,
|
|
271
263
|
imageBackgroundSrc: k,
|
|
272
|
-
enabledPercentUpdate:
|
|
264
|
+
enabledPercentUpdate: _,
|
|
273
265
|
onProgress: (g) => {
|
|
274
266
|
this.dispatchEvent(new CustomEvent("progress", { detail: { percent: g } }));
|
|
275
267
|
},
|
|
276
268
|
onComplete: () => {
|
|
277
269
|
this.dispatchEvent(new CustomEvent("complete", { detail: { percent: 100 } }));
|
|
278
270
|
}
|
|
279
|
-
}), this.instance.init(), (!
|
|
271
|
+
}), this.instance.init(), (!n || !r) && "ResizeObserver" in window ? (this.resizeObserver?.disconnect(), this.resizeObserver = new ResizeObserver(() => {
|
|
280
272
|
if (this.hasAttribute("width") && this.hasAttribute("height")) {
|
|
281
273
|
this.resizeObserver?.disconnect(), this.resizeObserver = void 0;
|
|
282
274
|
return;
|
|
283
275
|
}
|
|
284
|
-
const g = this.getBoundingClientRect(),
|
|
285
|
-
this.instance?.resize(
|
|
286
|
-
const
|
|
276
|
+
const g = this.getBoundingClientRect(), v = Math.round(g.width), p = Math.round(g.height);
|
|
277
|
+
this.instance?.resize(v, p);
|
|
278
|
+
const x = e(
|
|
287
279
|
this.getAttribute("brush-size"),
|
|
280
|
+
v,
|
|
288
281
|
p,
|
|
289
|
-
f,
|
|
290
282
|
a.brushSize
|
|
291
283
|
);
|
|
292
|
-
this.instance?.setBrushSize(
|
|
284
|
+
this.instance?.setBrushSize(x);
|
|
293
285
|
}), this.resizeObserver.observe(this)) : (this.resizeObserver?.disconnect(), this.resizeObserver = void 0);
|
|
294
286
|
}
|
|
295
287
|
}
|
|
296
|
-
customElements.define(
|
|
288
|
+
customElements.define(h, s);
|
|
297
289
|
}
|
|
298
|
-
function B(
|
|
299
|
-
|
|
290
|
+
function B(h) {
|
|
291
|
+
M(), h.config.globalProperties.$scratchReveal = !0;
|
|
300
292
|
}
|
|
301
293
|
export {
|
|
302
294
|
B as installScratchReveal,
|
|
303
|
-
|
|
304
|
-
|
|
295
|
+
M as registerScratchRevealElement,
|
|
296
|
+
L as scratchRevealCssText
|
|
305
297
|
};
|
package/dist/index.umd.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
(function(
|
|
1
|
+
(function(o,l){typeof exports=="object"&&typeof module<"u"?l(exports):typeof define=="function"&&define.amd?define(["exports"],l):(o=typeof globalThis<"u"?globalThis:o||self,l(o.ScratchReveal={}))})(this,(function(o){"use strict";class l{ctx;mouseX;mouseY;constructor(t,e,s){this.ctx=t,this.mouseX=e,this.mouseY=s}updateMousePosition(t,e){this.mouseX=t,this.mouseY=e}brush(t,e=0){if(!t){const i=new Error("Brush.brush: img is required");console.log(i.message);return}const s=Math.atan2(this.mouseY,this.mouseX);if(this.ctx.save(),this.ctx.translate(this.mouseX,this.mouseY),this.ctx.rotate(s),e>0){const i=e,n=e*(t.height/t.width);this.ctx.drawImage(t,-(i/2),-(n/2),i,n)}else this.ctx.drawImage(t,-(t.width/2),-(t.height/2));this.ctx.restore()}}function v(h){return new Promise((t,e)=>{const s=new Image;s.crossOrigin="anonymous",s.onload=()=>t(s),s.onerror=()=>e(new Error(`Image ${h} failed to load`)),s.src=h})}function E(h){let t=0;return((...s)=>{t||(t=requestAnimationFrame(()=>{t=0,h(...s)}))})}const p=`.sr {
|
|
2
2
|
position: relative;
|
|
3
3
|
overflow: hidden;
|
|
4
4
|
width: 100%;
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|
scratch-reveal {
|
|
24
24
|
display: inline-block;
|
|
25
|
-
width:
|
|
26
|
-
height:
|
|
25
|
+
width: 100%;
|
|
26
|
+
height: 100%;
|
|
27
27
|
}
|
|
28
|
-
`,
|
|
28
|
+
`,y=p;function k(h){return"adoptedStyleSheets"in h}function x(h){if(typeof CSSStyleSheet>"u")return null;try{const t=new CSSStyleSheet;return t.replaceSync(h),t}catch{return null}}const f=x(p),a={width:300,height:300,brushSrc:"/demo/assets/brush.png",imageMaskSrc:"/demo/assets/scratch-reveal.png",imageBackgroundSrc:"/demo/assets/scratch-reveal-background.svg",brushSize:0,percentToFinish:60,enabledPercentUpdate:!0};class C{config;ctx;container;_canvas;brush;maskImage;backgroundImage;brushImage;backgroundEl;brushSize=0;percent=0;done=!1;destroyed=!1;removeListeners;get canvas(){return this._canvas}constructor(t,e={}){if(this.config={...a,...e},this.container=typeof t=="string"?document.querySelector(t):t,!this.container)throw new Error("ScratchReveal: container not found");this._canvas=this.createCanvas(this.config.width,this.config.height),this.ctx=this._canvas.getContext("2d",{willReadFrequently:!0}),this.brush=new l(this.ctx,0,0),this.brushSize=this.config.brushSize,this.container.appendChild(this._canvas)}async init(){const[t,e,s]=await Promise.all([v(this.config.brushSrc),v(this.config.imageMaskSrc),v(this.config.imageBackgroundSrc)]);return this.destroyed?this:(this.brushImage=t,this.maskImage=e,this.backgroundImage=s,this.drawMask(),this.setBackground(),this.bindEvents(),this)}destroy(){this.destroyed=!0,this.removeListeners?.()}getPercent(){return this.percent}createCanvas(t,e){const s=document.createElement("canvas");return s.className="sr__canvas",s.width=t,s.height=e,s.style.width="100%",s.style.height="100%",s}resize(t,e){this.destroyed||this.done||t<=0||e<=0||this._canvas.width===t&&this._canvas.height===e||(this._canvas.width=t,this._canvas.height=e,this.percent=0,this.ctx.globalCompositeOperation="source-over",this.drawMask())}setBrushSize(t){this.destroyed||!Number.isFinite(t)||t<0||(this.brushSize=t)}bindEvents(){const t=E(i=>{this.updatePosition(i),this.scratch(),this.config.enabledPercentUpdate&&(this.percent=this.updatePercent(),this.config.onProgress?.(this.percent)),this.finish(i,t)}),e=i=>{i.preventDefault(),this.updatePosition(i),this.scratch(),this._canvas.setPointerCapture(i.pointerId),this._canvas.addEventListener("pointermove",t),this.config.enabledPercentUpdate&&(this.percent=this.updatePercent(),this.config.onProgress?.(this.percent)),this.finish(i,t)},s=i=>{this.finish(i,t)};this._canvas.addEventListener("pointerdown",e),this._canvas.addEventListener("pointerup",s),this._canvas.addEventListener("pointerleave",s),this.removeListeners=()=>{this._canvas.removeEventListener("pointerdown",e),this._canvas.removeEventListener("pointerup",s),this._canvas.removeEventListener("pointerleave",s),this._canvas.removeEventListener("pointermove",t)}}updatePosition(t){const e=this._canvas.getBoundingClientRect(),s=e.width?this._canvas.width/e.width:1,i=e.height?this._canvas.height/e.height:1,n=(t.clientX-e.left)*s,r=(t.clientY-e.top)*i;this.brush.updateMousePosition(n,r)}drawMask(){this.maskImage&&(this.ctx.globalCompositeOperation="source-over",this.ctx.clearRect(0,0,this._canvas.width,this._canvas.height),this.ctx.drawImage(this.maskImage,0,0,this._canvas.width,this._canvas.height))}setBackground(){if(this.destroyed||!this.backgroundImage||!this.container.contains(this._canvas))return;const t=this.backgroundEl??document.createElement("img");t.src=this.backgroundImage.src,t.className="sr__bg",t.isConnected||this.container.insertBefore(t,this._canvas),this.backgroundEl=t}scratch(){this.brushImage&&(this.ctx.globalCompositeOperation="destination-out",this.ctx.save(),this.brush.brush(this.brushImage,this.brushSize),this.ctx.restore())}updatePercent(){const e=this.ctx.getImageData(0,0,this._canvas.width,this._canvas.height).data;let s=0;for(let i=3;i<e.length;i+=4)e[i]===0&&s++;return s/(this._canvas.width*this._canvas.height)*100}finish(t,e){if(!this.done&&this.percent>this.config.percentToFinish&&(this.done=!0,this.clear(),this._canvas.style.pointerEvents="none",this.config.onComplete?.(),t&&e)){try{this._canvas.releasePointerCapture(t.pointerId)}catch{}this._canvas.removeEventListener("pointermove",e)}}clear(){this.ctx.clearRect(0,0,this._canvas.width,this._canvas.height)}}function S(h="scratch-reveal"){if(typeof window>"u"||!("customElements"in window)||customElements.get(h))return;function t(i,n){if(i===null)return n;const r=i.trim().toLowerCase();return r===""?!0:r==="false"||r==="0"||r==="no"||r==="off"?!1:r==="true"||r==="1"||r==="yes"||r==="on"?!0:n}function e(i,n,r,c){if(!i)return c;const u=i.trim();if(!u)return c;if(u.endsWith("%")){const d=Number.parseFloat(u.slice(0,-1));if(!Number.isFinite(d))return c;const g=Math.min(n,r);return Math.max(0,g*d/100)}const m=u.endsWith("px")?Number.parseFloat(u.slice(0,-2)):Number.parseFloat(u);return Number.isFinite(m)?Math.max(0,m):c}class s extends HTMLElement{instance;container;styleEl=null;rebuildScheduled=!1;resizeObserver;static get observedAttributes(){return["width","height","percent-to-finish","brush-src","brush-size","mask-src","background-src","enabled-percent-update"]}constructor(){super();const n=this.attachShadow({mode:"open"});k(n)&&f?n.adoptedStyleSheets=[f]:(this.styleEl=document.createElement("style"),this.styleEl.textContent=p,n.append(this.styleEl)),this.container=document.createElement("div"),this.container.className="sr",n.append(this.container)}connectedCallback(){this.scheduleRebuild()}disconnectedCallback(){this.instance?.destroy(),this.instance=void 0,this.resizeObserver?.disconnect(),this.resizeObserver=void 0}attributeChangedCallback(n,r,c){r!==c&&this.scheduleRebuild()}scheduleRebuild(){this.rebuildScheduled||(this.rebuildScheduled=!0,queueMicrotask(()=>{this.rebuildScheduled=!1,this.isConnected&&this.rebuild()}))}rebuild(){this.container.replaceChildren(),this.instance?.destroy();const n=this.hasAttribute("width"),r=this.hasAttribute("height"),c=this.getBoundingClientRect(),u=Math.round(c.width),m=Math.round(c.height),d=n?Number(this.getAttribute("width")):u||a.width,g=r?Number(this.getAttribute("height")):m||a.height,P=Number(this.getAttribute("percent-to-finish")??a.percentToFinish),A=this.getAttribute("brush-src")??a.brushSrc,R=t(this.getAttribute("enabled-percent-update"),a.enabledPercentUpdate),I=e(this.getAttribute("brush-size"),d,g,a.brushSize),M=this.getAttribute("mask-src")??a.imageMaskSrc,T=this.getAttribute("background-src")??a.imageBackgroundSrc;n?this.container.style.width=`${d}px`:this.container.style.width="100%",r?this.container.style.height=`${g}px`:this.container.style.height="100%",this.instance=new C(this.container,{width:d,height:g,percentToFinish:P,brushSrc:A,brushSize:I,imageMaskSrc:M,imageBackgroundSrc:T,enabledPercentUpdate:R,onProgress:b=>{this.dispatchEvent(new CustomEvent("progress",{detail:{percent:b}}))},onComplete:()=>{this.dispatchEvent(new CustomEvent("complete",{detail:{percent:100}}))}}),this.instance.init(),(!n||!r)&&"ResizeObserver"in window?(this.resizeObserver?.disconnect(),this.resizeObserver=new ResizeObserver(()=>{if(this.hasAttribute("width")&&this.hasAttribute("height")){this.resizeObserver?.disconnect(),this.resizeObserver=void 0;return}const b=this.getBoundingClientRect(),w=Math.round(b.width),_=Math.round(b.height);this.instance?.resize(w,_);const L=e(this.getAttribute("brush-size"),w,_,a.brushSize);this.instance?.setBrushSize(L)}),this.resizeObserver.observe(this)):(this.resizeObserver?.disconnect(),this.resizeObserver=void 0)}}customElements.define(h,s)}function z(h){S(),h.config.globalProperties.$scratchReveal=!0}o.installScratchReveal=z,o.registerScratchRevealElement=S,o.scratchRevealCssText=y,Object.defineProperty(o,Symbol.toStringTag,{value:"Module"})}));
|
package/dist/options.d.ts
CHANGED
|
@@ -4,9 +4,6 @@ export interface ScratchRevealOptions {
|
|
|
4
4
|
imageMaskSrc: string;
|
|
5
5
|
imageBackgroundSrc: string;
|
|
6
6
|
brushSrc: string;
|
|
7
|
-
/**
|
|
8
|
-
* Brush width in CSS pixels. Use `0` to keep the brush image natural size.
|
|
9
|
-
*/
|
|
10
7
|
brushSize: number;
|
|
11
8
|
percentToFinish: number;
|
|
12
9
|
enabledPercentUpdate: boolean;
|
package/dist/scratch-reveal.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
.sr{width:100%;height:100%;position:relative;overflow:hidden}.sr__bg{object-fit:cover;width:100%;height:100%;display:block;position:relative}.sr__canvas{width:100%;height:100%;position:absolute;inset:0}scratch-reveal{width:
|
|
1
|
+
.sr{width:100%;height:100%;position:relative;overflow:hidden}.sr__bg{object-fit:cover;width:100%;height:100%;display:block;position:relative}.sr__canvas{width:100%;height:100%;position:absolute;inset:0}scratch-reveal{width:100%;height:100%;display:inline-block}
|
package/package.json
CHANGED