scratch-reveal 1.1.0 → 1.2.1-dev.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/index.cjs.js +10 -2
- package/dist/index.es.js +67 -66
- package/dist/index.umd.js +10 -2
- 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 M{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)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,h=e*(t.height/t.width);this.ctx.drawImage(t,-(i/2),-(h/2),i,h)}else this.ctx.drawImage(t,-(t.width/2),-(t.height/2));this.ctx.restore()}}function v(r){return new Promise((t,e)=>{const s=new Image;s.crossOrigin="anonymous",s.onload=()=>t(s),s.onerror=()=>e(new Error(`Image ${r} failed to load`)),s.src=r})}function E(r){let t=0;return((...s)=>{t||(t=requestAnimationFrame(()=>{t=0,r(...s)}))})}const p=`.sr {
|
|
2
2
|
position: relative;
|
|
3
3
|
overflow: hidden;
|
|
4
4
|
width: 100%;
|
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
width: 100%;
|
|
12
12
|
height: 100%;
|
|
13
13
|
object-fit: cover;
|
|
14
|
+
user-select: none;
|
|
15
|
+
-webkit-user-select: none;
|
|
16
|
+
-webkit-user-drag: none;
|
|
17
|
+
pointer-events: none;
|
|
14
18
|
}
|
|
15
19
|
|
|
16
20
|
.sr__canvas {
|
|
@@ -18,6 +22,10 @@
|
|
|
18
22
|
inset: 0;
|
|
19
23
|
width: 100%;
|
|
20
24
|
height: 100%;
|
|
25
|
+
touch-action: none;
|
|
26
|
+
user-select: none;
|
|
27
|
+
-webkit-user-select: none;
|
|
28
|
+
-webkit-tap-highlight-color: transparent;
|
|
21
29
|
}
|
|
22
30
|
|
|
23
31
|
scratch-reveal {
|
|
@@ -25,4 +33,4 @@ scratch-reveal {
|
|
|
25
33
|
width: 100%;
|
|
26
34
|
height: 100%;
|
|
27
35
|
}
|
|
28
|
-
`,
|
|
36
|
+
`,R=p;function z(r){return"adoptedStyleSheets"in r}function P(r){if(typeof CSSStyleSheet>"u")return null;try{const t=new CSSStyleSheet;return t.replaceSync(r),t}catch{return null}}const _=P(p),u={width:300,height:300,brushSrc:"",imageMaskSrc:"",imageBackgroundSrc:"",brushSize:0,percentToFinish:60};class A{config;ctx;container;_canvas;brush;maskImage;backgroundImage;brushImage;backgroundEl;brushSize=0;percent=0;done=!1;completing=!1;destroyed=!1;lastProgressEmitMs=0;removeListeners;get canvas(){return this._canvas}constructor(t,e={}){if(this.config={...u,...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 M(this.ctx,0,0),this.brushSize=this.config.brushSize,this.container.appendChild(this._canvas)}async init(){if(!this.config.brushSrc)throw new Error('ScratchReveal: "brushSrc" is required');if(!this.config.imageMaskSrc)throw new Error('ScratchReveal: "imageMaskSrc" is required');if(!this.config.imageBackgroundSrc)throw new Error('ScratchReveal: "imageBackgroundSrc" is required');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 e=E((n,a)=>{this.percent=this.updatePercent();const o=performance.now();(!this.lastProgressEmitMs||o-this.lastProgressEmitMs>=120)&&(this.lastProgressEmitMs=o,this.config.onProgress?.(this.percent)),this.finish(n,a)}),s=E(n=>{(n.buttons&1)!==0&&(this.updatePosition(n),this.scratch(),e(n,s))}),i=n=>{try{this._canvas.releasePointerCapture(n.pointerId)}catch{}this._canvas.removeEventListener("pointermove",s),this.percent=this.updatePercent(),this.config.onProgress?.(this.percent),this.finish(n,s)},h=n=>{n.preventDefault(),this.updatePosition(n),this.scratch(),this._canvas.setPointerCapture(n.pointerId),this._canvas.addEventListener("pointermove",s),this.percent=this.updatePercent(),this.config.onProgress?.(this.percent),this.finish(n,s)};this._canvas.addEventListener("pointerdown",h),this._canvas.addEventListener("pointerup",i),this._canvas.addEventListener("pointercancel",i),this.removeListeners=()=>{this._canvas.removeEventListener("pointerdown",h),this._canvas.removeEventListener("pointerup",i),this._canvas.removeEventListener("pointercancel",i),this._canvas.removeEventListener("pointermove",s)}}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,h=(t.clientX-e.left)*s,n=(t.clientY-e.top)*i;this.brush.updateMousePosition(h,n)}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(){if(!this.brushImage)return;const t=this.brushSize>0?this.brushSize:0;this.ctx.globalCompositeOperation="destination-out",this.ctx.save(),this.brush.brush(this.brushImage,t),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._canvas.style.pointerEvents="none",this.config.onComplete?.(),this.playCompleteEffect(),t&&e)){try{this._canvas.releasePointerCapture(t.pointerId)}catch{}this._canvas.removeEventListener("pointermove",e)}}playCompleteEffect(){if(this.destroyed||this.completing)return;this.completing=!0;const t=350,e="ease-out";this._canvas.style.transition=`opacity ${t}ms ${e}`,this._canvas.style.opacity||(this._canvas.style.opacity="1"),requestAnimationFrame(()=>{this._canvas.style.opacity="0",window.setTimeout(()=>{this.destroyed||this.clear()},t)})}clear(){this.ctx.clearRect(0,0,this._canvas.width,this._canvas.height)}}function y(r="scratch-reveal"){if(typeof window>"u"||!("customElements"in window)||customElements.get(r))return;function t(s,i,h,n){if(!s)return n;const a=s.trim();if(!a)return n;const o=Number.parseFloat(a.endsWith("%")?a.slice(0,-1):a.endsWith("px")?a.slice(0,-2):a);if(!Number.isFinite(o))return n;if(a.endsWith("px"))return Math.max(0,o);const d=Math.min(i,h);return Math.max(0,d*o/100)}class e extends HTMLElement{instance;container;styleEl=null;rebuildScheduled=!1;resizeObserver;lastErrorMessage=null;static get observedAttributes(){return["width","height","complete-percent","brush-src","brush-size","mask-src","background-src"]}constructor(){super();const i=this.attachShadow({mode:"open"});z(i)&&_?i.adoptedStyleSheets=[_]:(this.styleEl=document.createElement("style"),this.styleEl.textContent=p,i.append(this.styleEl)),this.container=document.createElement("div"),this.container.className="sr",i.append(this.container)}connectedCallback(){this.scheduleRebuild()}disconnectedCallback(){this.instance?.destroy(),this.instance=void 0,this.resizeObserver?.disconnect(),this.resizeObserver=void 0}attributeChangedCallback(i,h,n){h!==n&&this.scheduleRebuild()}scheduleRebuild(){this.rebuildScheduled||(this.rebuildScheduled=!0,queueMicrotask(()=>{this.rebuildScheduled=!1,this.isConnected&&this.rebuild()}))}rebuild(){this.container.replaceChildren(),this.instance?.destroy();const i=this.hasAttribute("width"),h=this.hasAttribute("height"),n=this.getBoundingClientRect(),a=Math.round(n.width),o=Math.round(n.height),d=i?Number(this.getAttribute("width")):a||u.width,m=h?Number(this.getAttribute("height")):o||u.height,k=Number(this.getAttribute("complete-percent")??u.percentToFinish),b=(this.getAttribute("brush-src")??"").trim(),x=t(this.getAttribute("brush-size"),d,m,u.brushSize),f=(this.getAttribute("mask-src")??"").trim(),S=(this.getAttribute("background-src")??"").trim(),l=[];if(b||l.push("brush-src"),f||l.push("mask-src"),S||l.push("background-src"),l.length){const c=`ScratchReveal: missing required attribute(s): ${l.join(", ")}`;this.renderError(c);return}i?this.container.style.width=`${d}px`:this.container.style.width="100%",h?this.container.style.height=`${m}px`:this.container.style.height="100%",this.instance=new A(this.container,{width:d,height:m,percentToFinish:k,brushSrc:b,brushSize:x,imageMaskSrc:f,imageBackgroundSrc:S,onProgress:c=>{this.dispatchEvent(new CustomEvent("progress",{detail:{percent:c}}))},onComplete:()=>{this.dispatchEvent(new CustomEvent("complete",{detail:{percent:100}}))}}),this.instance.init().catch(c=>{const g=c instanceof Error?c.message:"ScratchReveal: init failed";this.renderError(g)}),(!i||!h)&&"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 c=this.getBoundingClientRect(),g=Math.round(c.width),w=Math.round(c.height);this.instance?.resize(g,w);const C=t(this.getAttribute("brush-size"),g,w,u.brushSize);this.instance?.setBrushSize(C)}),this.resizeObserver.observe(this)):(this.resizeObserver?.disconnect(),this.resizeObserver=void 0)}renderError(i){this.instance?.destroy(),this.instance=void 0,this.lastErrorMessage!==i&&(this.lastErrorMessage=i,this.dispatchEvent(new CustomEvent("error",{detail:{message:i}})))}}customElements.define(r,e)}function I(r){y(),r.config.globalProperties.$scratchReveal=!0}exports.installScratchReveal=I;exports.registerScratchRevealElement=y;exports.scratchRevealCssText=R;
|
package/dist/index.es.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
class
|
|
1
|
+
class C {
|
|
2
2
|
ctx;
|
|
3
3
|
mouseX;
|
|
4
4
|
mouseY;
|
|
@@ -13,28 +13,28 @@ class M {
|
|
|
13
13
|
return;
|
|
14
14
|
const s = Math.atan2(this.mouseY, this.mouseX);
|
|
15
15
|
if (this.ctx.save(), this.ctx.translate(this.mouseX, this.mouseY), this.ctx.rotate(s), e > 0) {
|
|
16
|
-
const i = e,
|
|
17
|
-
this.ctx.drawImage(t, -(i / 2), -(
|
|
16
|
+
const i = e, h = e * (t.height / t.width);
|
|
17
|
+
this.ctx.drawImage(t, -(i / 2), -(h / 2), i, h);
|
|
18
18
|
} else
|
|
19
19
|
this.ctx.drawImage(t, -(t.width / 2), -(t.height / 2));
|
|
20
20
|
this.ctx.restore();
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
|
-
function v(
|
|
23
|
+
function v(r) {
|
|
24
24
|
return new Promise((t, e) => {
|
|
25
25
|
const s = new Image();
|
|
26
|
-
s.crossOrigin = "anonymous", s.onload = () => t(s), s.onerror = () => e(new Error(`Image ${
|
|
26
|
+
s.crossOrigin = "anonymous", s.onload = () => t(s), s.onerror = () => e(new Error(`Image ${r} failed to load`)), s.src = r;
|
|
27
27
|
});
|
|
28
28
|
}
|
|
29
|
-
function E(
|
|
29
|
+
function E(r) {
|
|
30
30
|
let t = 0;
|
|
31
31
|
return ((...s) => {
|
|
32
32
|
t || (t = requestAnimationFrame(() => {
|
|
33
|
-
t = 0,
|
|
33
|
+
t = 0, r(...s);
|
|
34
34
|
}));
|
|
35
35
|
});
|
|
36
36
|
}
|
|
37
|
-
const
|
|
37
|
+
const p = `.sr {
|
|
38
38
|
position: relative;
|
|
39
39
|
overflow: hidden;
|
|
40
40
|
width: 100%;
|
|
@@ -47,6 +47,10 @@ const b = `.sr {
|
|
|
47
47
|
width: 100%;
|
|
48
48
|
height: 100%;
|
|
49
49
|
object-fit: cover;
|
|
50
|
+
user-select: none;
|
|
51
|
+
-webkit-user-select: none;
|
|
52
|
+
-webkit-user-drag: none;
|
|
53
|
+
pointer-events: none;
|
|
50
54
|
}
|
|
51
55
|
|
|
52
56
|
.sr__canvas {
|
|
@@ -54,6 +58,10 @@ const b = `.sr {
|
|
|
54
58
|
inset: 0;
|
|
55
59
|
width: 100%;
|
|
56
60
|
height: 100%;
|
|
61
|
+
touch-action: none;
|
|
62
|
+
user-select: none;
|
|
63
|
+
-webkit-user-select: none;
|
|
64
|
+
-webkit-tap-highlight-color: transparent;
|
|
57
65
|
}
|
|
58
66
|
|
|
59
67
|
scratch-reveal {
|
|
@@ -61,20 +69,20 @@ scratch-reveal {
|
|
|
61
69
|
width: 100%;
|
|
62
70
|
height: 100%;
|
|
63
71
|
}
|
|
64
|
-
`,
|
|
65
|
-
function
|
|
66
|
-
return "adoptedStyleSheets" in
|
|
72
|
+
`, I = p;
|
|
73
|
+
function M(r) {
|
|
74
|
+
return "adoptedStyleSheets" in r;
|
|
67
75
|
}
|
|
68
|
-
function
|
|
76
|
+
function z(r) {
|
|
69
77
|
if (typeof CSSStyleSheet > "u") return null;
|
|
70
78
|
try {
|
|
71
79
|
const t = new CSSStyleSheet();
|
|
72
|
-
return t.replaceSync(
|
|
80
|
+
return t.replaceSync(r), t;
|
|
73
81
|
} catch {
|
|
74
82
|
return null;
|
|
75
83
|
}
|
|
76
84
|
}
|
|
77
|
-
const _ =
|
|
85
|
+
const _ = z(p), u = {
|
|
78
86
|
width: 300,
|
|
79
87
|
height: 300,
|
|
80
88
|
brushSrc: "",
|
|
@@ -99,8 +107,6 @@ class R {
|
|
|
99
107
|
completing = !1;
|
|
100
108
|
destroyed = !1;
|
|
101
109
|
lastProgressEmitMs = 0;
|
|
102
|
-
lastScratchX;
|
|
103
|
-
lastScratchY;
|
|
104
110
|
removeListeners;
|
|
105
111
|
get canvas() {
|
|
106
112
|
return this._canvas;
|
|
@@ -110,7 +116,7 @@ class R {
|
|
|
110
116
|
throw new Error("ScratchReveal: container not found");
|
|
111
117
|
this._canvas = this.createCanvas(this.config.width, this.config.height), this.ctx = this._canvas.getContext("2d", {
|
|
112
118
|
willReadFrequently: !0
|
|
113
|
-
}), this.brush = new
|
|
119
|
+
}), this.brush = new C(this.ctx, 0, 0), this.brushSize = this.config.brushSize, this.container.appendChild(this._canvas);
|
|
114
120
|
}
|
|
115
121
|
async init() {
|
|
116
122
|
if (!this.config.brushSrc)
|
|
@@ -144,25 +150,29 @@ class R {
|
|
|
144
150
|
}
|
|
145
151
|
bindEvents() {
|
|
146
152
|
const e = E(
|
|
147
|
-
(
|
|
153
|
+
(n, a) => {
|
|
148
154
|
this.percent = this.updatePercent();
|
|
149
155
|
const o = performance.now();
|
|
150
|
-
(!this.lastProgressEmitMs || o - this.lastProgressEmitMs >= 120) && (this.lastProgressEmitMs = o, this.config.onProgress?.(this.percent)), this.finish(
|
|
156
|
+
(!this.lastProgressEmitMs || o - this.lastProgressEmitMs >= 120) && (this.lastProgressEmitMs = o, this.config.onProgress?.(this.percent)), this.finish(n, a);
|
|
151
157
|
}
|
|
152
|
-
), s = E((
|
|
153
|
-
this.updatePosition(
|
|
154
|
-
}), i = (
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
+
), s = E((n) => {
|
|
159
|
+
(n.buttons & 1) !== 0 && (this.updatePosition(n), this.scratch(), e(n, s));
|
|
160
|
+
}), i = (n) => {
|
|
161
|
+
try {
|
|
162
|
+
this._canvas.releasePointerCapture(n.pointerId);
|
|
163
|
+
} catch {
|
|
164
|
+
}
|
|
165
|
+
this._canvas.removeEventListener("pointermove", s), this.percent = this.updatePercent(), this.config.onProgress?.(this.percent), this.finish(n, s);
|
|
166
|
+
}, h = (n) => {
|
|
167
|
+
n.preventDefault(), this.updatePosition(n), this.scratch(), this._canvas.setPointerCapture(n.pointerId), this._canvas.addEventListener("pointermove", s), this.percent = this.updatePercent(), this.config.onProgress?.(this.percent), this.finish(n, s);
|
|
158
168
|
};
|
|
159
|
-
this._canvas.addEventListener("pointerdown",
|
|
160
|
-
this._canvas.removeEventListener("pointerdown",
|
|
169
|
+
this._canvas.addEventListener("pointerdown", h), this._canvas.addEventListener("pointerup", i), this._canvas.addEventListener("pointercancel", i), this.removeListeners = () => {
|
|
170
|
+
this._canvas.removeEventListener("pointerdown", h), this._canvas.removeEventListener("pointerup", i), this._canvas.removeEventListener("pointercancel", i), this._canvas.removeEventListener("pointermove", s);
|
|
161
171
|
};
|
|
162
172
|
}
|
|
163
173
|
updatePosition(t) {
|
|
164
|
-
const e = this._canvas.getBoundingClientRect(), s = e.width ? this._canvas.width / e.width : 1, i = e.height ? this._canvas.height / e.height : 1,
|
|
165
|
-
this.brush.updateMousePosition(
|
|
174
|
+
const e = this._canvas.getBoundingClientRect(), s = e.width ? this._canvas.width / e.width : 1, i = e.height ? this._canvas.height / e.height : 1, h = (t.clientX - e.left) * s, n = (t.clientY - e.top) * i;
|
|
175
|
+
this.brush.updateMousePosition(h, n);
|
|
166
176
|
}
|
|
167
177
|
drawMask() {
|
|
168
178
|
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));
|
|
@@ -174,17 +184,8 @@ class R {
|
|
|
174
184
|
}
|
|
175
185
|
scratch() {
|
|
176
186
|
if (!this.brushImage) return;
|
|
177
|
-
const t = this.brushSize > 0 ? this.brushSize :
|
|
178
|
-
|
|
179
|
-
const i = this.brush.mouseX - this.lastScratchX, n = this.brush.mouseY - this.lastScratchY;
|
|
180
|
-
if (i * i + n * n < e * e)
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
const s = this.brushSize > 0 ? this.brushSize : 0;
|
|
184
|
-
this.ctx.globalCompositeOperation = "destination-out", this.ctx.save(), this.brush.brush(this.brushImage, s), this.ctx.restore(), this.lastScratchX = this.brush.mouseX, this.lastScratchY = this.brush.mouseY;
|
|
185
|
-
}
|
|
186
|
-
resetScratchTrail() {
|
|
187
|
-
this.lastScratchX = void 0, this.lastScratchY = void 0;
|
|
187
|
+
const t = this.brushSize > 0 ? this.brushSize : 0;
|
|
188
|
+
this.ctx.globalCompositeOperation = "destination-out", this.ctx.save(), this.brush.brush(this.brushImage, t), this.ctx.restore();
|
|
188
189
|
}
|
|
189
190
|
updatePercent() {
|
|
190
191
|
const e = this.ctx.getImageData(0, 0, this._canvas.width, this._canvas.height).data;
|
|
@@ -216,19 +217,19 @@ class R {
|
|
|
216
217
|
this.ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
|
|
217
218
|
}
|
|
218
219
|
}
|
|
219
|
-
function
|
|
220
|
-
if (typeof window > "u" || !("customElements" in window) || customElements.get(
|
|
221
|
-
function t(s, i,
|
|
222
|
-
if (!s) return
|
|
220
|
+
function P(r = "scratch-reveal") {
|
|
221
|
+
if (typeof window > "u" || !("customElements" in window) || customElements.get(r)) return;
|
|
222
|
+
function t(s, i, h, n) {
|
|
223
|
+
if (!s) return n;
|
|
223
224
|
const a = s.trim();
|
|
224
|
-
if (!a) return
|
|
225
|
+
if (!a) return n;
|
|
225
226
|
const o = Number.parseFloat(
|
|
226
227
|
a.endsWith("%") ? a.slice(0, -1) : a.endsWith("px") ? a.slice(0, -2) : a
|
|
227
228
|
);
|
|
228
|
-
if (!Number.isFinite(o)) return
|
|
229
|
+
if (!Number.isFinite(o)) return n;
|
|
229
230
|
if (a.endsWith("px"))
|
|
230
231
|
return Math.max(0, o);
|
|
231
|
-
const d = Math.min(i,
|
|
232
|
+
const d = Math.min(i, h);
|
|
232
233
|
return Math.max(0, d * o / 100);
|
|
233
234
|
}
|
|
234
235
|
class e extends HTMLElement {
|
|
@@ -252,7 +253,7 @@ function I(h = "scratch-reveal") {
|
|
|
252
253
|
constructor() {
|
|
253
254
|
super();
|
|
254
255
|
const i = this.attachShadow({ mode: "open" });
|
|
255
|
-
|
|
256
|
+
M(i) && _ ? i.adoptedStyleSheets = [_] : (this.styleEl = document.createElement("style"), this.styleEl.textContent = p, i.append(this.styleEl)), this.container = document.createElement("div"), this.container.className = "sr", i.append(this.container);
|
|
256
257
|
}
|
|
257
258
|
connectedCallback() {
|
|
258
259
|
this.scheduleRebuild();
|
|
@@ -260,8 +261,8 @@ function I(h = "scratch-reveal") {
|
|
|
260
261
|
disconnectedCallback() {
|
|
261
262
|
this.instance?.destroy(), this.instance = void 0, this.resizeObserver?.disconnect(), this.resizeObserver = void 0;
|
|
262
263
|
}
|
|
263
|
-
attributeChangedCallback(i,
|
|
264
|
-
|
|
264
|
+
attributeChangedCallback(i, h, n) {
|
|
265
|
+
h !== n && this.scheduleRebuild();
|
|
265
266
|
}
|
|
266
267
|
scheduleRebuild() {
|
|
267
268
|
this.rebuildScheduled || (this.rebuildScheduled = !0, queueMicrotask(() => {
|
|
@@ -270,27 +271,27 @@ function I(h = "scratch-reveal") {
|
|
|
270
271
|
}
|
|
271
272
|
rebuild() {
|
|
272
273
|
this.container.replaceChildren(), this.instance?.destroy();
|
|
273
|
-
const i = this.hasAttribute("width"),
|
|
274
|
+
const i = this.hasAttribute("width"), h = this.hasAttribute("height"), n = this.getBoundingClientRect(), a = Math.round(n.width), o = Math.round(n.height), d = i ? Number(this.getAttribute("width")) : a || u.width, m = h ? Number(this.getAttribute("height")) : o || u.height, y = Number(
|
|
274
275
|
this.getAttribute("complete-percent") ?? u.percentToFinish
|
|
275
|
-
),
|
|
276
|
+
), b = (this.getAttribute("brush-src") ?? "").trim(), k = t(
|
|
276
277
|
this.getAttribute("brush-size"),
|
|
277
278
|
d,
|
|
278
279
|
m,
|
|
279
280
|
u.brushSize
|
|
280
|
-
), f = (this.getAttribute("mask-src") ?? "").trim(),
|
|
281
|
-
if (
|
|
281
|
+
), f = (this.getAttribute("mask-src") ?? "").trim(), w = (this.getAttribute("background-src") ?? "").trim(), l = [];
|
|
282
|
+
if (b || l.push("brush-src"), f || l.push("mask-src"), w || l.push("background-src"), l.length) {
|
|
282
283
|
const c = `ScratchReveal: missing required attribute(s): ${l.join(", ")}`;
|
|
283
284
|
this.renderError(c);
|
|
284
285
|
return;
|
|
285
286
|
}
|
|
286
|
-
i ? this.container.style.width = `${d}px` : this.container.style.width = "100%",
|
|
287
|
+
i ? this.container.style.width = `${d}px` : this.container.style.width = "100%", h ? this.container.style.height = `${m}px` : this.container.style.height = "100%", this.instance = new R(this.container, {
|
|
287
288
|
width: d,
|
|
288
289
|
height: m,
|
|
289
290
|
percentToFinish: y,
|
|
290
|
-
brushSrc:
|
|
291
|
+
brushSrc: b,
|
|
291
292
|
brushSize: k,
|
|
292
293
|
imageMaskSrc: f,
|
|
293
|
-
imageBackgroundSrc:
|
|
294
|
+
imageBackgroundSrc: w,
|
|
294
295
|
onProgress: (c) => {
|
|
295
296
|
this.dispatchEvent(new CustomEvent("progress", { detail: { percent: c } }));
|
|
296
297
|
},
|
|
@@ -300,17 +301,17 @@ function I(h = "scratch-reveal") {
|
|
|
300
301
|
}), this.instance.init().catch((c) => {
|
|
301
302
|
const g = c instanceof Error ? c.message : "ScratchReveal: init failed";
|
|
302
303
|
this.renderError(g);
|
|
303
|
-
}), (!i || !
|
|
304
|
+
}), (!i || !h) && "ResizeObserver" in window ? (this.resizeObserver?.disconnect(), this.resizeObserver = new ResizeObserver(() => {
|
|
304
305
|
if (this.hasAttribute("width") && this.hasAttribute("height")) {
|
|
305
306
|
this.resizeObserver?.disconnect(), this.resizeObserver = void 0;
|
|
306
307
|
return;
|
|
307
308
|
}
|
|
308
|
-
const c = this.getBoundingClientRect(), g = Math.round(c.width),
|
|
309
|
-
this.instance?.resize(g,
|
|
309
|
+
const c = this.getBoundingClientRect(), g = Math.round(c.width), S = Math.round(c.height);
|
|
310
|
+
this.instance?.resize(g, S);
|
|
310
311
|
const x = t(
|
|
311
312
|
this.getAttribute("brush-size"),
|
|
312
313
|
g,
|
|
313
|
-
|
|
314
|
+
S,
|
|
314
315
|
u.brushSize
|
|
315
316
|
);
|
|
316
317
|
this.instance?.setBrushSize(x);
|
|
@@ -320,13 +321,13 @@ function I(h = "scratch-reveal") {
|
|
|
320
321
|
this.instance?.destroy(), this.instance = void 0, this.lastErrorMessage !== i && (this.lastErrorMessage = i, this.dispatchEvent(new CustomEvent("error", { detail: { message: i } })));
|
|
321
322
|
}
|
|
322
323
|
}
|
|
323
|
-
customElements.define(
|
|
324
|
+
customElements.define(r, e);
|
|
324
325
|
}
|
|
325
|
-
function T(
|
|
326
|
-
|
|
326
|
+
function T(r) {
|
|
327
|
+
P(), r.config.globalProperties.$scratchReveal = !0;
|
|
327
328
|
}
|
|
328
329
|
export {
|
|
329
330
|
T as installScratchReveal,
|
|
330
|
-
|
|
331
|
-
|
|
331
|
+
P as registerScratchRevealElement,
|
|
332
|
+
I as scratchRevealCssText
|
|
332
333
|
};
|
package/dist/index.umd.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
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)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,
|
|
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)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,h=e*(t.height/t.width);this.ctx.drawImage(t,-(i/2),-(h/2),i,h)}else this.ctx.drawImage(t,-(t.width/2),-(t.height/2));this.ctx.restore()}}function f(r){return new Promise((t,e)=>{const s=new Image;s.crossOrigin="anonymous",s.onload=()=>t(s),s.onerror=()=>e(new Error(`Image ${r} failed to load`)),s.src=r})}function S(r){let t=0;return((...s)=>{t||(t=requestAnimationFrame(()=>{t=0,r(...s)}))})}const p=`.sr {
|
|
2
2
|
position: relative;
|
|
3
3
|
overflow: hidden;
|
|
4
4
|
width: 100%;
|
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
width: 100%;
|
|
12
12
|
height: 100%;
|
|
13
13
|
object-fit: cover;
|
|
14
|
+
user-select: none;
|
|
15
|
+
-webkit-user-select: none;
|
|
16
|
+
-webkit-user-drag: none;
|
|
17
|
+
pointer-events: none;
|
|
14
18
|
}
|
|
15
19
|
|
|
16
20
|
.sr__canvas {
|
|
@@ -18,6 +22,10 @@
|
|
|
18
22
|
inset: 0;
|
|
19
23
|
width: 100%;
|
|
20
24
|
height: 100%;
|
|
25
|
+
touch-action: none;
|
|
26
|
+
user-select: none;
|
|
27
|
+
-webkit-user-select: none;
|
|
28
|
+
-webkit-tap-highlight-color: transparent;
|
|
21
29
|
}
|
|
22
30
|
|
|
23
31
|
scratch-reveal {
|
|
@@ -25,4 +33,4 @@ scratch-reveal {
|
|
|
25
33
|
width: 100%;
|
|
26
34
|
height: 100%;
|
|
27
35
|
}
|
|
28
|
-
`,
|
|
36
|
+
`,C=p;function R(r){return"adoptedStyleSheets"in r}function M(r){if(typeof CSSStyleSheet>"u")return null;try{const t=new CSSStyleSheet;return t.replaceSync(r),t}catch{return null}}const w=M(p),d={width:300,height:300,brushSrc:"",imageMaskSrc:"",imageBackgroundSrc:"",brushSize:0,percentToFinish:60};class z{config;ctx;container;_canvas;brush;maskImage;backgroundImage;brushImage;backgroundEl;brushSize=0;percent=0;done=!1;completing=!1;destroyed=!1;lastProgressEmitMs=0;removeListeners;get canvas(){return this._canvas}constructor(t,e={}){if(this.config={...d,...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(){if(!this.config.brushSrc)throw new Error('ScratchReveal: "brushSrc" is required');if(!this.config.imageMaskSrc)throw new Error('ScratchReveal: "imageMaskSrc" is required');if(!this.config.imageBackgroundSrc)throw new Error('ScratchReveal: "imageBackgroundSrc" is required');const[t,e,s]=await Promise.all([f(this.config.brushSrc),f(this.config.imageMaskSrc),f(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 e=S((n,a)=>{this.percent=this.updatePercent();const u=performance.now();(!this.lastProgressEmitMs||u-this.lastProgressEmitMs>=120)&&(this.lastProgressEmitMs=u,this.config.onProgress?.(this.percent)),this.finish(n,a)}),s=S(n=>{(n.buttons&1)!==0&&(this.updatePosition(n),this.scratch(),e(n,s))}),i=n=>{try{this._canvas.releasePointerCapture(n.pointerId)}catch{}this._canvas.removeEventListener("pointermove",s),this.percent=this.updatePercent(),this.config.onProgress?.(this.percent),this.finish(n,s)},h=n=>{n.preventDefault(),this.updatePosition(n),this.scratch(),this._canvas.setPointerCapture(n.pointerId),this._canvas.addEventListener("pointermove",s),this.percent=this.updatePercent(),this.config.onProgress?.(this.percent),this.finish(n,s)};this._canvas.addEventListener("pointerdown",h),this._canvas.addEventListener("pointerup",i),this._canvas.addEventListener("pointercancel",i),this.removeListeners=()=>{this._canvas.removeEventListener("pointerdown",h),this._canvas.removeEventListener("pointerup",i),this._canvas.removeEventListener("pointercancel",i),this._canvas.removeEventListener("pointermove",s)}}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,h=(t.clientX-e.left)*s,n=(t.clientY-e.top)*i;this.brush.updateMousePosition(h,n)}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(){if(!this.brushImage)return;const t=this.brushSize>0?this.brushSize:0;this.ctx.globalCompositeOperation="destination-out",this.ctx.save(),this.brush.brush(this.brushImage,t),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._canvas.style.pointerEvents="none",this.config.onComplete?.(),this.playCompleteEffect(),t&&e)){try{this._canvas.releasePointerCapture(t.pointerId)}catch{}this._canvas.removeEventListener("pointermove",e)}}playCompleteEffect(){if(this.destroyed||this.completing)return;this.completing=!0;const t=350,e="ease-out";this._canvas.style.transition=`opacity ${t}ms ${e}`,this._canvas.style.opacity||(this._canvas.style.opacity="1"),requestAnimationFrame(()=>{this._canvas.style.opacity="0",window.setTimeout(()=>{this.destroyed||this.clear()},t)})}clear(){this.ctx.clearRect(0,0,this._canvas.width,this._canvas.height)}}function E(r="scratch-reveal"){if(typeof window>"u"||!("customElements"in window)||customElements.get(r))return;function t(s,i,h,n){if(!s)return n;const a=s.trim();if(!a)return n;const u=Number.parseFloat(a.endsWith("%")?a.slice(0,-1):a.endsWith("px")?a.slice(0,-2):a);if(!Number.isFinite(u))return n;if(a.endsWith("px"))return Math.max(0,u);const g=Math.min(i,h);return Math.max(0,g*u/100)}class e extends HTMLElement{instance;container;styleEl=null;rebuildScheduled=!1;resizeObserver;lastErrorMessage=null;static get observedAttributes(){return["width","height","complete-percent","brush-src","brush-size","mask-src","background-src"]}constructor(){super();const i=this.attachShadow({mode:"open"});R(i)&&w?i.adoptedStyleSheets=[w]:(this.styleEl=document.createElement("style"),this.styleEl.textContent=p,i.append(this.styleEl)),this.container=document.createElement("div"),this.container.className="sr",i.append(this.container)}connectedCallback(){this.scheduleRebuild()}disconnectedCallback(){this.instance?.destroy(),this.instance=void 0,this.resizeObserver?.disconnect(),this.resizeObserver=void 0}attributeChangedCallback(i,h,n){h!==n&&this.scheduleRebuild()}scheduleRebuild(){this.rebuildScheduled||(this.rebuildScheduled=!0,queueMicrotask(()=>{this.rebuildScheduled=!1,this.isConnected&&this.rebuild()}))}rebuild(){this.container.replaceChildren(),this.instance?.destroy();const i=this.hasAttribute("width"),h=this.hasAttribute("height"),n=this.getBoundingClientRect(),a=Math.round(n.width),u=Math.round(n.height),g=i?Number(this.getAttribute("width")):a||d.width,b=h?Number(this.getAttribute("height")):u||d.height,A=Number(this.getAttribute("complete-percent")??d.percentToFinish),_=(this.getAttribute("brush-src")??"").trim(),I=t(this.getAttribute("brush-size"),g,b,d.brushSize),y=(this.getAttribute("mask-src")??"").trim(),k=(this.getAttribute("background-src")??"").trim(),m=[];if(_||m.push("brush-src"),y||m.push("mask-src"),k||m.push("background-src"),m.length){const c=`ScratchReveal: missing required attribute(s): ${m.join(", ")}`;this.renderError(c);return}i?this.container.style.width=`${g}px`:this.container.style.width="100%",h?this.container.style.height=`${b}px`:this.container.style.height="100%",this.instance=new z(this.container,{width:g,height:b,percentToFinish:A,brushSrc:_,brushSize:I,imageMaskSrc:y,imageBackgroundSrc:k,onProgress:c=>{this.dispatchEvent(new CustomEvent("progress",{detail:{percent:c}}))},onComplete:()=>{this.dispatchEvent(new CustomEvent("complete",{detail:{percent:100}}))}}),this.instance.init().catch(c=>{const v=c instanceof Error?c.message:"ScratchReveal: init failed";this.renderError(v)}),(!i||!h)&&"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 c=this.getBoundingClientRect(),v=Math.round(c.width),x=Math.round(c.height);this.instance?.resize(v,x);const T=t(this.getAttribute("brush-size"),v,x,d.brushSize);this.instance?.setBrushSize(T)}),this.resizeObserver.observe(this)):(this.resizeObserver?.disconnect(),this.resizeObserver=void 0)}renderError(i){this.instance?.destroy(),this.instance=void 0,this.lastErrorMessage!==i&&(this.lastErrorMessage=i,this.dispatchEvent(new CustomEvent("error",{detail:{message:i}})))}}customElements.define(r,e)}function P(r){E(),r.config.globalProperties.$scratchReveal=!0}o.installScratchReveal=P,o.registerScratchRevealElement=E,o.scratchRevealCssText=C,Object.defineProperty(o,Symbol.toStringTag,{value:"Module"})}));
|
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:100%;height:100%;display:inline-block}
|
|
1
|
+
.sr{width:100%;height:100%;position:relative;overflow:hidden}.sr__bg{object-fit:cover;-webkit-user-select:none;user-select:none;-webkit-user-drag:none;pointer-events:none;width:100%;height:100%;display:block;position:relative}.sr__canvas{touch-action:none;-webkit-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent;width:100%;height:100%;position:absolute;inset:0}scratch-reveal{width:100%;height:100%;display:inline-block}
|
package/package.json
CHANGED