scratch-reveal 1.3.0-dev.4 → 1.3.0-dev.5
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 +2 -2
- package/dist/index.es.js +44 -36
- package/dist/index.umd.js +2 -2
- 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 y{ctx;mouseX;mouseY;constructor(t,s,i){this.ctx=t,this.mouseX=s,this.mouseY=i}updateMousePosition(t,s){this.mouseX=t,this.mouseY=s}brush(t,s=0){if(!t)return;const i=Math.atan2(this.mouseY,this.mouseX);if(this.ctx.save(),this.ctx.translate(this.mouseX,this.mouseY),this.ctx.rotate(i),s>0){const e=s,r=s*(t.height/t.width);this.ctx.drawImage(t,-(e/2),-(r/2),e,r)}else this.ctx.drawImage(t,-(t.width/2),-(t.height/2));this.ctx.restore()}}function
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});class y{ctx;mouseX;mouseY;constructor(t,s,i){this.ctx=t,this.mouseX=s,this.mouseY=i}updateMousePosition(t,s){this.mouseX=t,this.mouseY=s}brush(t,s=0){if(!t)return;const i=Math.atan2(this.mouseY,this.mouseX);if(this.ctx.save(),this.ctx.translate(this.mouseX,this.mouseY),this.ctx.rotate(i),s>0){const e=s,r=s*(t.height/t.width);this.ctx.drawImage(t,-(e/2),-(r/2),e,r)}else this.ctx.drawImage(t,-(t.width/2),-(t.height/2));this.ctx.restore()}}function m(o){return new Promise((t,s)=>{const i=new Image;i.crossOrigin="anonymous",i.onload=()=>t(i),i.onerror=()=>s(new Error(`Image ${o} failed to load`)),i.src=o})}function S(o){let t=0;return((...i)=>{t||(t=requestAnimationFrame(()=>{t=0,o(...i)}))})}const f=`.sr {
|
|
2
2
|
position: relative;
|
|
3
3
|
overflow: hidden;
|
|
4
4
|
width: 100%;
|
|
@@ -58,4 +58,4 @@ scratch-reveal {
|
|
|
58
58
|
width: 100%;
|
|
59
59
|
height: 100%;
|
|
60
60
|
}
|
|
61
|
-
`,E=v;function z(o){return"adoptedStyleSheets"in o}function k(o){if(typeof CSSStyleSheet>"u")return null;try{const t=new CSSStyleSheet;return t.replaceSync(o),t}catch{return null}}const w=k(v),g={width:300,height:300,brushSrc:"",imageMaskSrc:"",imageBackgroundSrc:"",brushSize:0,percentToFinish:60};class x{config;ctx;container;_canvas;dpr=1;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,s={}){if(this.config={...g,...s},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.setCanvasScale(this.config.width,this.config.height),this.brush=new y(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,s,i]=await Promise.all([p(this.config.brushSrc),p(this.config.imageMaskSrc),p(this.config.imageBackgroundSrc)]);return this.destroyed?this:(this.brushImage=t,this.maskImage=s,this.backgroundImage=i,this.drawMask(),this.setBackground(),this.bindEvents(),this)}destroy(){this.destroyed=!0,this.removeListeners?.()}getPercent(){return this.percent}createCanvas(t,s){const i=document.createElement("canvas");return i.className="sr__canvas",i.style.width="100%",i.style.height="100%",i}setCanvasScale(t,s){const i=Math.max(1,Math.min(window.devicePixelRatio||1,3));this.dpr=i,this._canvas.width=Math.round(t*this.dpr),this._canvas.height=Math.round(s*this.dpr),this._canvas.style.width=`${t}px`,this._canvas.style.height=`${s}px`,this.ctx.setTransform(this.dpr,0,0,this.dpr,0,0)}resize(t,s){this.destroyed||this.done||t<=0||s<=0||this._canvas.width===t*this.dpr&&this._canvas.height===s*this.dpr||(this.setCanvasScale(t,s),this.percent=0,this.ctx.globalCompositeOperation="source-over",this.drawMask())}setBrushSize(t){this.destroyed||!Number.isFinite(t)||t<0||(this.brushSize=t)}bindEvents(){const s=S((n,h)=>{this.percent=this.updatePercent();const a=performance.now();(!this.lastProgressEmitMs||a-this.lastProgressEmitMs>=120)&&(this.lastProgressEmitMs=a,this.config.onProgress?.(this.percent)),this.finish(n,h)}),i=S(n=>{(n.buttons&1)!==0&&(this.updatePosition(n),this.scratch(),s(n,i))}),e=n=>{try{this._canvas.releasePointerCapture(n.pointerId)}catch{}this._canvas.removeEventListener("pointermove",i),this.percent=this.updatePercent(),this.config.onProgress?.(this.percent),this.finish(n,i)},r=n=>{n.preventDefault(),this.updatePosition(n),this.scratch(),this._canvas.setPointerCapture(n.pointerId),this._canvas.addEventListener("pointermove",i),this.percent=this.updatePercent(),this.config.onProgress?.(this.percent),this.finish(n,i)};this._canvas.addEventListener("pointerdown",r),this._canvas.addEventListener("pointerup",e),this._canvas.addEventListener("pointercancel",e),this.removeListeners=()=>{this._canvas.removeEventListener("pointerdown",r),this._canvas.removeEventListener("pointerup",e),this._canvas.removeEventListener("pointercancel",e),this._canvas.removeEventListener("pointermove",i)}}updatePosition(t){const s=this._canvas.getBoundingClientRect(),i=s.width?this._canvas.width/s.width:1,e=s.height?this._canvas.height/s.height:1,r=(t.clientX-s.left)*i/this.dpr,n=(t.clientY-s.top)*e/this.dpr;this.brush.updateMousePosition(r,n)}drawMask(){this.maskImage&&(this.ctx.globalCompositeOperation="source-over",this.ctx.clearRect(0,0,this._canvas.width/this.dpr,this._canvas.height/this.dpr),this.ctx.drawImage(this.maskImage,0,0,this._canvas.width/this.dpr,this._canvas.height/this.dpr))}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 s=this.ctx.getImageData(0,0,this._canvas.width,this._canvas.height).data;let i=0;for(let e=3;e<s.length;e+=4)s[e]===0&&i++;return i/(this._canvas.width*this._canvas.height)*100}finish(t,s){if(!this.done&&this.percent>this.config.percentToFinish&&(this.done=!0,this._canvas.style.pointerEvents="none",this.config.onComplete?.(),this.playCompleteEffect(),t&&s)){try{this._canvas.releasePointerCapture(t.pointerId)}catch{}this._canvas.removeEventListener("pointermove",s)}}playCompleteEffect(){if(this.destroyed||this.completing)return;this.completing=!0;const t=350,s="ease-out";this._canvas.style.transition=`opacity ${t}ms ${s}`,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.dpr,this._canvas.height/this.dpr)}}function _(o="scratch-reveal"){if(typeof window>"u"||!("customElements"in window)||customElements.get(o))return;function t(i,e,r,n){if(!i)return n;const h=i.trim();if(!h)return n;const a=Number.parseFloat(h.endsWith("%")?h.slice(0,-1):h.endsWith("px")?h.slice(0,-2):h);if(!Number.isFinite(a))return n;if(h.endsWith("px"))return Math.max(0,a);const c=Math.min(e,r);return Math.max(0,c*a/100)}class s extends HTMLElement{instance;container;styleEl=null;rebuildScheduled=!1;rebuildToken=0;resizeObserver;lastErrorMessage=null;static get observedAttributes(){return["width","height","complete-percent","brush-src","brush-size","mask-src","background-src","show-status"]}constructor(){super();const e=this.attachShadow({mode:"open"});z(e)&&w?e.adoptedStyleSheets=[w]:(this.styleEl=document.createElement("style"),this.styleEl.textContent=v,e.append(this.styleEl)),this.container=document.createElement("div"),this.container.className="sr",e.append(this.container)}connectedCallback(){this.scheduleRebuild()}disconnectedCallback(){this.instance?.destroy(),this.instance=void 0,this.resizeObserver?.disconnect(),this.resizeObserver=void 0}attributeChangedCallback(e,r,n){if(r!==n){if(e==="brush-size"&&this.instance){const{width:h,height:a}=this.resolveSize(),c=t(this.getAttribute("brush-size"),h,a,g.brushSize);this.instance.setBrushSize(c);return}if((e==="width"||e==="height")&&this.instance){const h=this.resolveSize();this.syncContainerSize(h),this.instance.resize(h.width,h.height);const a=t(this.getAttribute("brush-size"),h.width,h.height,g.brushSize);this.instance.setBrushSize(a),this.updateResizeObserver(h);return}this.scheduleRebuild()}}scheduleRebuild(){this.rebuildScheduled||(this.rebuildScheduled=!0,queueMicrotask(()=>{this.rebuildScheduled=!1,this.isConnected&&this.rebuild()}))}async rebuild(){const e=++this.rebuildToken,r=this.resolveSize(),n=this.resolvePercentToFinish(),{brushSrc:h,imageMaskSrc:a,imageBackgroundSrc:c}=this.resolveSources(),l=[];if(h||l.push("brush-src"),a||l.push("mask-src"),c||l.push("background-src"),l.length){this.syncContainerSize(r),this.renderWaiting(l),this.updateResizeObserver(r);return}let u=r;if(!r.hasWidthAttr&&!r.hasHeightAttr&&r.autoWidth===0&&r.autoHeight===0)try{const d=await p(a);if(e!==this.rebuildToken||!this.isConnected)return;const b=d.naturalWidth||d.width,f=d.naturalHeight||d.height;b>0&&f>0&&(u={...r,width:b,height:f})}catch{}this.syncContainerSize(u);const m=t(this.getAttribute("brush-size"),u.width,u.height,g.brushSize);this.container.replaceChildren(),this.instance?.destroy(),this.instance=void 0,this.lastErrorMessage=null,this.instance=new x(this.container,{width:u.width,height:u.height,percentToFinish:n,brushSrc:h,brushSize:m,imageMaskSrc:a,imageBackgroundSrc:c,onProgress:d=>{this.dispatchEvent(new CustomEvent("progress",{detail:{percent:d},bubbles:!0,composed:!0}))},onComplete:()=>{this.dispatchEvent(new CustomEvent("complete",{detail:{percent:100},bubbles:!0,composed:!0}))}}),this.instance.init().catch(d=>{const b=d instanceof Error?d.message:"ScratchReveal: init failed";this.renderError(b)}),this.updateResizeObserver(u)}renderError(e){this.instance?.destroy(),this.instance=void 0,this.renderStatus(e,"error"),this.lastErrorMessage!==e&&(this.lastErrorMessage=e,this.dispatchEvent(new CustomEvent("error",{detail:{message:e},bubbles:!0,composed:!0})))}renderWaiting(e){const r=`ScratchReveal: waiting for required attribute(s): ${e.join(", ")}`;this.renderStatus(r,"waiting")}renderStatus(e,r){if(this.container.replaceChildren(),this.instance?.destroy(),this.instance=void 0,!this.hasAttribute("show-status"))return;const n=document.createElement("div");n.className=`sr__status sr__status--${r}`,n.textContent=e,n.setAttribute("role",r==="error"?"alert":"status"),this.container.appendChild(n)}resolveSize(){const e=this.hasAttribute("width"),r=this.hasAttribute("height"),n=this.getBoundingClientRect(),h=Math.round(n.width),a=Math.round(n.height),c=Number(this.getAttribute("width")),l=Number(this.getAttribute("height")),u=e&&Number.isFinite(c)?c:h||g.width,m=r&&Number.isFinite(l)?l:a||g.height;return{hasWidthAttr:e,hasHeightAttr:r,width:u,height:m,autoWidth:h,autoHeight:a}}resolvePercentToFinish(){const e=Number(this.getAttribute("complete-percent"));return Number.isFinite(e)?e:g.percentToFinish}resolveSources(){return{brushSrc:(this.getAttribute("brush-src")??"").trim(),imageMaskSrc:(this.getAttribute("mask-src")??"").trim(),imageBackgroundSrc:(this.getAttribute("background-src")??"").trim()}}syncContainerSize(e){e.hasWidthAttr?this.container.style.width=`${e.width}px`:this.container.style.width="100%",e.hasHeightAttr?this.container.style.height=`${e.height}px`:this.container.style.height="100%"}updateResizeObserver(e){(!e.hasWidthAttr||!e.hasHeightAttr)&&"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 n=this.getBoundingClientRect(),h=Math.round(n.width),a=Math.round(n.height);this.instance?.resize(h,a);const c=t(this.getAttribute("brush-size"),h,a,g.brushSize);this.instance?.setBrushSize(c)}),this.resizeObserver.observe(this)):(this.resizeObserver?.disconnect(),this.resizeObserver=void 0)}}customElements.define(o,s)}function C(o){_(),o.config.globalProperties.$scratchReveal=!0}exports.installScratchReveal=C;exports.registerScratchRevealElement=_;exports.scratchRevealCssText=E;
|
|
61
|
+
`,E=f;function z(o){return"adoptedStyleSheets"in o}function k(o){if(typeof CSSStyleSheet>"u")return null;try{const t=new CSSStyleSheet;return t.replaceSync(o),t}catch{return null}}const w=k(f),g={width:300,height:300,brushSrc:"",imageMaskSrc:"",imageBackgroundSrc:"",brushSize:0,percentToFinish:60};class C{config;ctx;container;_canvas;dpr=1;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,s={}){if(this.config={...g,...s},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.setCanvasScale(this.config.width,this.config.height),this.brush=new y(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,s,i]=await Promise.all([m(this.config.brushSrc),m(this.config.imageMaskSrc),m(this.config.imageBackgroundSrc)]);return this.destroyed?this:(this.brushImage=t,this.maskImage=s,this.backgroundImage=i,this.drawMask(),this.setBackground(),this.bindEvents(),this)}destroy(){this.destroyed=!0,this.removeListeners?.()}getPercent(){return this.percent}createCanvas(t,s){const i=document.createElement("canvas");return i.className="sr__canvas",i.style.width="100%",i.style.height="100%",i}setCanvasScale(t,s){const i=Math.max(1,Math.min(window.devicePixelRatio||1,3));this.dpr=i,this._canvas.width=Math.round(t*this.dpr),this._canvas.height=Math.round(s*this.dpr),this._canvas.style.width=`${t}px`,this._canvas.style.height=`${s}px`,this.ctx.setTransform(this.dpr,0,0,this.dpr,0,0)}resize(t,s){this.destroyed||this.done||t<=0||s<=0||this._canvas.width===t*this.dpr&&this._canvas.height===s*this.dpr||(this.setCanvasScale(t,s),this.percent=0,this.ctx.globalCompositeOperation="source-over",this.drawMask())}setBrushSize(t){this.destroyed||!Number.isFinite(t)||t<0||(this.brushSize=t)}bindEvents(){const s=S((n,h)=>{this.percent=this.updatePercent();const a=performance.now();(!this.lastProgressEmitMs||a-this.lastProgressEmitMs>=120)&&(this.lastProgressEmitMs=a,this.config.onProgress?.(this.percent)),this.finish(n,h)}),i=S(n=>{(n.buttons&1)!==0&&(this.updatePosition(n),this.scratch(),s(n,i))}),e=n=>{try{this._canvas.releasePointerCapture(n.pointerId)}catch{}this._canvas.removeEventListener("pointermove",i),this.percent=this.updatePercent(),this.config.onProgress?.(this.percent),this.finish(n,i)},r=n=>{n.preventDefault(),this.updatePosition(n),this.scratch(),this._canvas.setPointerCapture(n.pointerId),this._canvas.addEventListener("pointermove",i),this.percent=this.updatePercent(),this.config.onProgress?.(this.percent),this.finish(n,i)};this._canvas.addEventListener("pointerdown",r),this._canvas.addEventListener("pointerup",e),this._canvas.addEventListener("pointercancel",e),this.removeListeners=()=>{this._canvas.removeEventListener("pointerdown",r),this._canvas.removeEventListener("pointerup",e),this._canvas.removeEventListener("pointercancel",e),this._canvas.removeEventListener("pointermove",i)}}updatePosition(t){const s=this._canvas.getBoundingClientRect(),i=s.width?this._canvas.width/s.width:1,e=s.height?this._canvas.height/s.height:1,r=(t.clientX-s.left)*i/this.dpr,n=(t.clientY-s.top)*e/this.dpr;this.brush.updateMousePosition(r,n)}drawMask(){this.maskImage&&(this.ctx.globalCompositeOperation="source-over",this.ctx.clearRect(0,0,this._canvas.width/this.dpr,this._canvas.height/this.dpr),this.ctx.drawImage(this.maskImage,0,0,this._canvas.width/this.dpr,this._canvas.height/this.dpr))}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 s=this.ctx.getImageData(0,0,this._canvas.width,this._canvas.height).data;let i=0;for(let e=3;e<s.length;e+=4)s[e]===0&&i++;return i/(this._canvas.width*this._canvas.height)*100}finish(t,s){if(!this.done&&this.percent>this.config.percentToFinish&&(this.done=!0,this._canvas.style.pointerEvents="none",this.config.onComplete?.(),this.playCompleteEffect(),t&&s)){try{this._canvas.releasePointerCapture(t.pointerId)}catch{}this._canvas.removeEventListener("pointermove",s)}}playCompleteEffect(){if(this.destroyed||this.completing)return;this.completing=!0;const t=350,s="ease-out";this._canvas.style.transition=`opacity ${t}ms ${s}`,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.dpr,this._canvas.height/this.dpr)}}function _(o="scratch-reveal"){if(typeof window>"u"||!("customElements"in window)||customElements.get(o))return;function t(i,e,r,n){if(!i)return n;const h=i.trim();if(!h)return n;const a=Number.parseFloat(h.endsWith("%")?h.slice(0,-1):h.endsWith("px")?h.slice(0,-2):h);if(!Number.isFinite(a))return n;if(h.endsWith("px"))return Math.max(0,a);const u=Math.min(e,r);return Math.max(0,u*a/100)}class s extends HTMLElement{instance;container;styleEl=null;rebuildScheduled=!1;rebuildToken=0;resizeObserver;lastErrorMessage=null;static get observedAttributes(){return["width","height","complete-percent","brush-src","brush-size","mask-src","background-src","show-status"]}constructor(){super();const e=this.attachShadow({mode:"open"});z(e)&&w?e.adoptedStyleSheets=[w]:(this.styleEl=document.createElement("style"),this.styleEl.textContent=f,e.append(this.styleEl)),this.container=document.createElement("div"),this.container.className="sr",e.append(this.container)}connectedCallback(){this.scheduleRebuild()}disconnectedCallback(){this.instance?.destroy(),this.instance=void 0,this.resizeObserver?.disconnect(),this.resizeObserver=void 0}attributeChangedCallback(e,r,n){if(r!==n){if(e==="brush-size"&&this.instance){const{width:h,height:a}=this.resolveSize(),u=t(this.getAttribute("brush-size"),h,a,g.brushSize);this.instance.setBrushSize(u);return}if((e==="width"||e==="height")&&this.instance){const h=this.resolveSize();this.syncContainerSize(h),this.instance.resize(h.width,h.height);const a=t(this.getAttribute("brush-size"),h.width,h.height,g.brushSize);this.instance.setBrushSize(a),this.updateResizeObserver(h);return}this.scheduleRebuild()}}scheduleRebuild(){this.rebuildScheduled||(this.rebuildScheduled=!0,queueMicrotask(()=>{this.rebuildScheduled=!1,this.isConnected&&this.rebuild()}))}async rebuild(){const e=++this.rebuildToken,r=this.resolveSize(),n=this.resolvePercentToFinish(),{brushSrc:h,imageMaskSrc:a,imageBackgroundSrc:u}=this.resolveSources(),l=[];if(h||l.push("brush-src"),a||l.push("mask-src"),u||l.push("background-src"),l.length){this.syncContainerSize(r),this.renderWaiting(l),this.updateResizeObserver(r);return}let d=r;if(!r.hasWidthAttr&&!r.hasHeightAttr&&r.autoWidth===0&&r.autoHeight===0)try{const c=await m(a);if(e!==this.rebuildToken||!this.isConnected)return;const b=c.naturalWidth||c.width,p=c.naturalHeight||c.height;b>0&&p>0&&(d={...r,width:b,height:p})}catch{}if(!r.hasWidthAttr&&!r.hasHeightAttr){const c=this.getBoundingClientRect(),b=Math.round(c.width),p=Math.round(c.height);b>0&&p>0&&(d={...d,width:b,height:p})}this.syncContainerSize(d);const v=t(this.getAttribute("brush-size"),d.width,d.height,g.brushSize);this.container.replaceChildren(),this.instance?.destroy(),this.instance=void 0,this.lastErrorMessage=null,this.instance=new C(this.container,{width:d.width,height:d.height,percentToFinish:n,brushSrc:h,brushSize:v,imageMaskSrc:a,imageBackgroundSrc:u,onProgress:c=>{this.dispatchEvent(new CustomEvent("progress",{detail:{percent:c},bubbles:!0,composed:!0}))},onComplete:()=>{this.dispatchEvent(new CustomEvent("complete",{detail:{percent:100},bubbles:!0,composed:!0}))}}),this.instance.init().catch(c=>{const b=c instanceof Error?c.message:"ScratchReveal: init failed";this.renderError(b)}),this.updateResizeObserver(d)}renderError(e){this.instance?.destroy(),this.instance=void 0,this.renderStatus(e,"error"),this.lastErrorMessage!==e&&(this.lastErrorMessage=e,this.dispatchEvent(new CustomEvent("error",{detail:{message:e},bubbles:!0,composed:!0})))}renderWaiting(e){const r=`ScratchReveal: waiting for required attribute(s): ${e.join(", ")}`;this.renderStatus(r,"waiting")}renderStatus(e,r){if(this.container.replaceChildren(),this.instance?.destroy(),this.instance=void 0,!this.hasAttribute("show-status"))return;const n=document.createElement("div");n.className=`sr__status sr__status--${r}`,n.textContent=e,n.setAttribute("role",r==="error"?"alert":"status"),this.container.appendChild(n)}resolveSize(){const e=this.hasAttribute("width"),r=this.hasAttribute("height"),n=this.getBoundingClientRect(),h=Math.round(n.width),a=Math.round(n.height),u=Number(this.getAttribute("width")),l=Number(this.getAttribute("height")),d=e&&Number.isFinite(u)?u:h||g.width,v=r&&Number.isFinite(l)?l:a||g.height;return{hasWidthAttr:e,hasHeightAttr:r,width:d,height:v,autoWidth:h,autoHeight:a}}resolvePercentToFinish(){const e=Number(this.getAttribute("complete-percent"));return Number.isFinite(e)?e:g.percentToFinish}resolveSources(){return{brushSrc:(this.getAttribute("brush-src")??"").trim(),imageMaskSrc:(this.getAttribute("mask-src")??"").trim(),imageBackgroundSrc:(this.getAttribute("background-src")??"").trim()}}syncContainerSize(e){e.hasWidthAttr?this.container.style.width=`${e.width}px`:this.container.style.width="100%",e.hasHeightAttr?this.container.style.height=`${e.height}px`:this.container.style.height="100%"}updateResizeObserver(e){(!e.hasWidthAttr||!e.hasHeightAttr)&&"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 n=this.getBoundingClientRect(),h=Math.round(n.width),a=Math.round(n.height);this.instance?.resize(h,a);const u=t(this.getAttribute("brush-size"),h,a,g.brushSize);this.instance?.setBrushSize(u)}),this.resizeObserver.observe(this)):(this.resizeObserver?.disconnect(),this.resizeObserver=void 0)}}customElements.define(o,s)}function x(o){_(),o.config.globalProperties.$scratchReveal=!0}exports.installScratchReveal=x;exports.registerScratchRevealElement=_;exports.scratchRevealCssText=E;
|
package/dist/index.es.js
CHANGED
|
@@ -20,7 +20,7 @@ class _ {
|
|
|
20
20
|
this.ctx.restore();
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
|
-
function
|
|
23
|
+
function m(o) {
|
|
24
24
|
return new Promise((t, s) => {
|
|
25
25
|
const i = new Image();
|
|
26
26
|
i.crossOrigin = "anonymous", i.onload = () => t(i), i.onerror = () => s(new Error(`Image ${o} failed to load`)), i.src = o;
|
|
@@ -34,7 +34,7 @@ function S(o) {
|
|
|
34
34
|
}));
|
|
35
35
|
});
|
|
36
36
|
}
|
|
37
|
-
const
|
|
37
|
+
const f = `.sr {
|
|
38
38
|
position: relative;
|
|
39
39
|
overflow: hidden;
|
|
40
40
|
width: 100%;
|
|
@@ -94,7 +94,7 @@ scratch-reveal {
|
|
|
94
94
|
width: 100%;
|
|
95
95
|
height: 100%;
|
|
96
96
|
}
|
|
97
|
-
`, x =
|
|
97
|
+
`, x = f;
|
|
98
98
|
function y(o) {
|
|
99
99
|
return "adoptedStyleSheets" in o;
|
|
100
100
|
}
|
|
@@ -107,7 +107,7 @@ function E(o) {
|
|
|
107
107
|
return null;
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
|
-
const w = E(
|
|
110
|
+
const w = E(f), g = {
|
|
111
111
|
width: 300,
|
|
112
112
|
height: 300,
|
|
113
113
|
brushSrc: "",
|
|
@@ -152,9 +152,9 @@ class z {
|
|
|
152
152
|
if (!this.config.imageBackgroundSrc)
|
|
153
153
|
throw new Error('ScratchReveal: "imageBackgroundSrc" is required');
|
|
154
154
|
const [t, s, i] = await Promise.all([
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
155
|
+
m(this.config.brushSrc),
|
|
156
|
+
m(this.config.imageMaskSrc),
|
|
157
|
+
m(this.config.imageBackgroundSrc)
|
|
158
158
|
]);
|
|
159
159
|
return this.destroyed ? this : (this.brushImage = t, this.maskImage = s, this.backgroundImage = i, this.drawMask(), this.setBackground(), this.bindEvents(), this);
|
|
160
160
|
}
|
|
@@ -265,8 +265,8 @@ function k(o = "scratch-reveal") {
|
|
|
265
265
|
if (!Number.isFinite(a)) return n;
|
|
266
266
|
if (h.endsWith("px"))
|
|
267
267
|
return Math.max(0, a);
|
|
268
|
-
const
|
|
269
|
-
return Math.max(0,
|
|
268
|
+
const u = Math.min(e, r);
|
|
269
|
+
return Math.max(0, u * a / 100);
|
|
270
270
|
}
|
|
271
271
|
class s extends HTMLElement {
|
|
272
272
|
instance;
|
|
@@ -291,7 +291,7 @@ function k(o = "scratch-reveal") {
|
|
|
291
291
|
constructor() {
|
|
292
292
|
super();
|
|
293
293
|
const e = this.attachShadow({ mode: "open" });
|
|
294
|
-
y(e) && w ? e.adoptedStyleSheets = [w] : (this.styleEl = document.createElement("style"), this.styleEl.textContent =
|
|
294
|
+
y(e) && w ? e.adoptedStyleSheets = [w] : (this.styleEl = document.createElement("style"), this.styleEl.textContent = f, e.append(this.styleEl)), this.container = document.createElement("div"), this.container.className = "sr", e.append(this.container);
|
|
295
295
|
}
|
|
296
296
|
connectedCallback() {
|
|
297
297
|
this.scheduleRebuild();
|
|
@@ -302,13 +302,13 @@ function k(o = "scratch-reveal") {
|
|
|
302
302
|
attributeChangedCallback(e, r, n) {
|
|
303
303
|
if (r !== n) {
|
|
304
304
|
if (e === "brush-size" && this.instance) {
|
|
305
|
-
const { width: h, height: a } = this.resolveSize(),
|
|
305
|
+
const { width: h, height: a } = this.resolveSize(), u = t(
|
|
306
306
|
this.getAttribute("brush-size"),
|
|
307
307
|
h,
|
|
308
308
|
a,
|
|
309
309
|
g.brushSize
|
|
310
310
|
);
|
|
311
|
-
this.instance.setBrushSize(
|
|
311
|
+
this.instance.setBrushSize(u);
|
|
312
312
|
return;
|
|
313
313
|
}
|
|
314
314
|
if ((e === "width" || e === "height") && this.instance) {
|
|
@@ -332,43 +332,51 @@ function k(o = "scratch-reveal") {
|
|
|
332
332
|
}));
|
|
333
333
|
}
|
|
334
334
|
async rebuild() {
|
|
335
|
-
const e = ++this.rebuildToken, r = this.resolveSize(), n = this.resolvePercentToFinish(), { brushSrc: h, imageMaskSrc: a, imageBackgroundSrc:
|
|
336
|
-
if (h || l.push("brush-src"), a || l.push("mask-src"),
|
|
335
|
+
const e = ++this.rebuildToken, r = this.resolveSize(), n = this.resolvePercentToFinish(), { brushSrc: h, imageMaskSrc: a, imageBackgroundSrc: u } = this.resolveSources(), l = [];
|
|
336
|
+
if (h || l.push("brush-src"), a || l.push("mask-src"), u || l.push("background-src"), l.length) {
|
|
337
337
|
this.syncContainerSize(r), this.renderWaiting(l), this.updateResizeObserver(r);
|
|
338
338
|
return;
|
|
339
339
|
}
|
|
340
|
-
let
|
|
340
|
+
let d = r;
|
|
341
341
|
if (!r.hasWidthAttr && !r.hasHeightAttr && r.autoWidth === 0 && r.autoHeight === 0)
|
|
342
342
|
try {
|
|
343
|
-
const
|
|
343
|
+
const c = await m(a);
|
|
344
344
|
if (e !== this.rebuildToken || !this.isConnected) return;
|
|
345
|
-
const b =
|
|
346
|
-
b > 0 &&
|
|
345
|
+
const b = c.naturalWidth || c.width, p = c.naturalHeight || c.height;
|
|
346
|
+
b > 0 && p > 0 && (d = {
|
|
347
347
|
...r,
|
|
348
348
|
width: b,
|
|
349
|
-
height:
|
|
349
|
+
height: p
|
|
350
350
|
});
|
|
351
351
|
} catch {
|
|
352
352
|
}
|
|
353
|
-
|
|
354
|
-
|
|
353
|
+
if (!r.hasWidthAttr && !r.hasHeightAttr) {
|
|
354
|
+
const c = this.getBoundingClientRect(), b = Math.round(c.width), p = Math.round(c.height);
|
|
355
|
+
b > 0 && p > 0 && (d = {
|
|
356
|
+
...d,
|
|
357
|
+
width: b,
|
|
358
|
+
height: p
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
this.syncContainerSize(d);
|
|
362
|
+
const v = t(
|
|
355
363
|
this.getAttribute("brush-size"),
|
|
356
|
-
|
|
357
|
-
|
|
364
|
+
d.width,
|
|
365
|
+
d.height,
|
|
358
366
|
g.brushSize
|
|
359
367
|
);
|
|
360
368
|
this.container.replaceChildren(), this.instance?.destroy(), this.instance = void 0, this.lastErrorMessage = null, this.instance = new z(this.container, {
|
|
361
|
-
width:
|
|
362
|
-
height:
|
|
369
|
+
width: d.width,
|
|
370
|
+
height: d.height,
|
|
363
371
|
percentToFinish: n,
|
|
364
372
|
brushSrc: h,
|
|
365
|
-
brushSize:
|
|
373
|
+
brushSize: v,
|
|
366
374
|
imageMaskSrc: a,
|
|
367
|
-
imageBackgroundSrc:
|
|
368
|
-
onProgress: (
|
|
375
|
+
imageBackgroundSrc: u,
|
|
376
|
+
onProgress: (c) => {
|
|
369
377
|
this.dispatchEvent(
|
|
370
378
|
new CustomEvent("progress", {
|
|
371
|
-
detail: { percent:
|
|
379
|
+
detail: { percent: c },
|
|
372
380
|
bubbles: !0,
|
|
373
381
|
composed: !0
|
|
374
382
|
})
|
|
@@ -383,10 +391,10 @@ function k(o = "scratch-reveal") {
|
|
|
383
391
|
})
|
|
384
392
|
);
|
|
385
393
|
}
|
|
386
|
-
}), this.instance.init().catch((
|
|
387
|
-
const b =
|
|
394
|
+
}), this.instance.init().catch((c) => {
|
|
395
|
+
const b = c instanceof Error ? c.message : "ScratchReveal: init failed";
|
|
388
396
|
this.renderError(b);
|
|
389
|
-
}), this.updateResizeObserver(
|
|
397
|
+
}), this.updateResizeObserver(d);
|
|
390
398
|
}
|
|
391
399
|
renderError(e) {
|
|
392
400
|
this.instance?.destroy(), this.instance = void 0, this.renderStatus(e, "error"), this.lastErrorMessage !== e && (this.lastErrorMessage = e, this.dispatchEvent(
|
|
@@ -407,8 +415,8 @@ function k(o = "scratch-reveal") {
|
|
|
407
415
|
n.className = `sr__status sr__status--${r}`, n.textContent = e, n.setAttribute("role", r === "error" ? "alert" : "status"), this.container.appendChild(n);
|
|
408
416
|
}
|
|
409
417
|
resolveSize() {
|
|
410
|
-
const e = this.hasAttribute("width"), r = this.hasAttribute("height"), n = this.getBoundingClientRect(), h = Math.round(n.width), a = Math.round(n.height),
|
|
411
|
-
return { hasWidthAttr: e, hasHeightAttr: r, width:
|
|
418
|
+
const e = this.hasAttribute("width"), r = this.hasAttribute("height"), n = this.getBoundingClientRect(), h = Math.round(n.width), a = Math.round(n.height), u = Number(this.getAttribute("width")), l = Number(this.getAttribute("height")), d = e && Number.isFinite(u) ? u : h || g.width, v = r && Number.isFinite(l) ? l : a || g.height;
|
|
419
|
+
return { hasWidthAttr: e, hasHeightAttr: r, width: d, height: v, autoWidth: h, autoHeight: a };
|
|
412
420
|
}
|
|
413
421
|
resolvePercentToFinish() {
|
|
414
422
|
const e = Number(this.getAttribute("complete-percent"));
|
|
@@ -432,13 +440,13 @@ function k(o = "scratch-reveal") {
|
|
|
432
440
|
}
|
|
433
441
|
const n = this.getBoundingClientRect(), h = Math.round(n.width), a = Math.round(n.height);
|
|
434
442
|
this.instance?.resize(h, a);
|
|
435
|
-
const
|
|
443
|
+
const u = t(
|
|
436
444
|
this.getAttribute("brush-size"),
|
|
437
445
|
h,
|
|
438
446
|
a,
|
|
439
447
|
g.brushSize
|
|
440
448
|
);
|
|
441
|
-
this.instance?.setBrushSize(
|
|
449
|
+
this.instance?.setBrushSize(u);
|
|
442
450
|
}), this.resizeObserver.observe(this)) : (this.resizeObserver?.disconnect(), this.resizeObserver = void 0);
|
|
443
451
|
}
|
|
444
452
|
}
|
package/dist/index.umd.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
(function(l,
|
|
1
|
+
(function(l,m){typeof exports=="object"&&typeof module<"u"?m(exports):typeof define=="function"&&define.amd?define(["exports"],m):(l=typeof globalThis<"u"?globalThis:l||self,m(l.ScratchReveal={}))})(this,(function(l){"use strict";class m{ctx;mouseX;mouseY;constructor(t,s,i){this.ctx=t,this.mouseX=s,this.mouseY=i}updateMousePosition(t,s){this.mouseX=t,this.mouseY=s}brush(t,s=0){if(!t)return;const i=Math.atan2(this.mouseY,this.mouseX);if(this.ctx.save(),this.ctx.translate(this.mouseX,this.mouseY),this.ctx.rotate(i),s>0){const e=s,r=s*(t.height/t.width);this.ctx.drawImage(t,-(e/2),-(r/2),e,r)}else this.ctx.drawImage(t,-(t.width/2),-(t.height/2));this.ctx.restore()}}function f(o){return new Promise((t,s)=>{const i=new Image;i.crossOrigin="anonymous",i.onload=()=>t(i),i.onerror=()=>s(new Error(`Image ${o} failed to load`)),i.src=o})}function y(o){let t=0;return((...i)=>{t||(t=requestAnimationFrame(()=>{t=0,o(...i)}))})}const S=`.sr {
|
|
2
2
|
position: relative;
|
|
3
3
|
overflow: hidden;
|
|
4
4
|
width: 100%;
|
|
@@ -58,4 +58,4 @@ scratch-reveal {
|
|
|
58
58
|
width: 100%;
|
|
59
59
|
height: 100%;
|
|
60
60
|
}
|
|
61
|
-
`,z=f;function k(o){return"adoptedStyleSheets"in o}function x(o){if(typeof CSSStyleSheet>"u")return null;try{const t=new CSSStyleSheet;return t.replaceSync(o),t}catch{return null}}const y=x(f),g={width:300,height:300,brushSrc:"",imageMaskSrc:"",imageBackgroundSrc:"",brushSize:0,percentToFinish:60};class C{config;ctx;container;_canvas;dpr=1;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,s={}){if(this.config={...g,...s},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.setCanvasScale(this.config.width,this.config.height),this.brush=new b(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,s,i]=await Promise.all([m(this.config.brushSrc),m(this.config.imageMaskSrc),m(this.config.imageBackgroundSrc)]);return this.destroyed?this:(this.brushImage=t,this.maskImage=s,this.backgroundImage=i,this.drawMask(),this.setBackground(),this.bindEvents(),this)}destroy(){this.destroyed=!0,this.removeListeners?.()}getPercent(){return this.percent}createCanvas(t,s){const i=document.createElement("canvas");return i.className="sr__canvas",i.style.width="100%",i.style.height="100%",i}setCanvasScale(t,s){const i=Math.max(1,Math.min(window.devicePixelRatio||1,3));this.dpr=i,this._canvas.width=Math.round(t*this.dpr),this._canvas.height=Math.round(s*this.dpr),this._canvas.style.width=`${t}px`,this._canvas.style.height=`${s}px`,this.ctx.setTransform(this.dpr,0,0,this.dpr,0,0)}resize(t,s){this.destroyed||this.done||t<=0||s<=0||this._canvas.width===t*this.dpr&&this._canvas.height===s*this.dpr||(this.setCanvasScale(t,s),this.percent=0,this.ctx.globalCompositeOperation="source-over",this.drawMask())}setBrushSize(t){this.destroyed||!Number.isFinite(t)||t<0||(this.brushSize=t)}bindEvents(){const s=w((n,h)=>{this.percent=this.updatePercent();const a=performance.now();(!this.lastProgressEmitMs||a-this.lastProgressEmitMs>=120)&&(this.lastProgressEmitMs=a,this.config.onProgress?.(this.percent)),this.finish(n,h)}),i=w(n=>{(n.buttons&1)!==0&&(this.updatePosition(n),this.scratch(),s(n,i))}),e=n=>{try{this._canvas.releasePointerCapture(n.pointerId)}catch{}this._canvas.removeEventListener("pointermove",i),this.percent=this.updatePercent(),this.config.onProgress?.(this.percent),this.finish(n,i)},r=n=>{n.preventDefault(),this.updatePosition(n),this.scratch(),this._canvas.setPointerCapture(n.pointerId),this._canvas.addEventListener("pointermove",i),this.percent=this.updatePercent(),this.config.onProgress?.(this.percent),this.finish(n,i)};this._canvas.addEventListener("pointerdown",r),this._canvas.addEventListener("pointerup",e),this._canvas.addEventListener("pointercancel",e),this.removeListeners=()=>{this._canvas.removeEventListener("pointerdown",r),this._canvas.removeEventListener("pointerup",e),this._canvas.removeEventListener("pointercancel",e),this._canvas.removeEventListener("pointermove",i)}}updatePosition(t){const s=this._canvas.getBoundingClientRect(),i=s.width?this._canvas.width/s.width:1,e=s.height?this._canvas.height/s.height:1,r=(t.clientX-s.left)*i/this.dpr,n=(t.clientY-s.top)*e/this.dpr;this.brush.updateMousePosition(r,n)}drawMask(){this.maskImage&&(this.ctx.globalCompositeOperation="source-over",this.ctx.clearRect(0,0,this._canvas.width/this.dpr,this._canvas.height/this.dpr),this.ctx.drawImage(this.maskImage,0,0,this._canvas.width/this.dpr,this._canvas.height/this.dpr))}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 s=this.ctx.getImageData(0,0,this._canvas.width,this._canvas.height).data;let i=0;for(let e=3;e<s.length;e+=4)s[e]===0&&i++;return i/(this._canvas.width*this._canvas.height)*100}finish(t,s){if(!this.done&&this.percent>this.config.percentToFinish&&(this.done=!0,this._canvas.style.pointerEvents="none",this.config.onComplete?.(),this.playCompleteEffect(),t&&s)){try{this._canvas.releasePointerCapture(t.pointerId)}catch{}this._canvas.removeEventListener("pointermove",s)}}playCompleteEffect(){if(this.destroyed||this.completing)return;this.completing=!0;const t=350,s="ease-out";this._canvas.style.transition=`opacity ${t}ms ${s}`,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.dpr,this._canvas.height/this.dpr)}}function _(o="scratch-reveal"){if(typeof window>"u"||!("customElements"in window)||customElements.get(o))return;function t(i,e,r,n){if(!i)return n;const h=i.trim();if(!h)return n;const a=Number.parseFloat(h.endsWith("%")?h.slice(0,-1):h.endsWith("px")?h.slice(0,-2):h);if(!Number.isFinite(a))return n;if(h.endsWith("px"))return Math.max(0,a);const c=Math.min(e,r);return Math.max(0,c*a/100)}class s extends HTMLElement{instance;container;styleEl=null;rebuildScheduled=!1;rebuildToken=0;resizeObserver;lastErrorMessage=null;static get observedAttributes(){return["width","height","complete-percent","brush-src","brush-size","mask-src","background-src","show-status"]}constructor(){super();const e=this.attachShadow({mode:"open"});k(e)&&y?e.adoptedStyleSheets=[y]:(this.styleEl=document.createElement("style"),this.styleEl.textContent=f,e.append(this.styleEl)),this.container=document.createElement("div"),this.container.className="sr",e.append(this.container)}connectedCallback(){this.scheduleRebuild()}disconnectedCallback(){this.instance?.destroy(),this.instance=void 0,this.resizeObserver?.disconnect(),this.resizeObserver=void 0}attributeChangedCallback(e,r,n){if(r!==n){if(e==="brush-size"&&this.instance){const{width:h,height:a}=this.resolveSize(),c=t(this.getAttribute("brush-size"),h,a,g.brushSize);this.instance.setBrushSize(c);return}if((e==="width"||e==="height")&&this.instance){const h=this.resolveSize();this.syncContainerSize(h),this.instance.resize(h.width,h.height);const a=t(this.getAttribute("brush-size"),h.width,h.height,g.brushSize);this.instance.setBrushSize(a),this.updateResizeObserver(h);return}this.scheduleRebuild()}}scheduleRebuild(){this.rebuildScheduled||(this.rebuildScheduled=!0,queueMicrotask(()=>{this.rebuildScheduled=!1,this.isConnected&&this.rebuild()}))}async rebuild(){const e=++this.rebuildToken,r=this.resolveSize(),n=this.resolvePercentToFinish(),{brushSrc:h,imageMaskSrc:a,imageBackgroundSrc:c}=this.resolveSources(),p=[];if(h||p.push("brush-src"),a||p.push("mask-src"),c||p.push("background-src"),p.length){this.syncContainerSize(r),this.renderWaiting(p),this.updateResizeObserver(r);return}let u=r;if(!r.hasWidthAttr&&!r.hasHeightAttr&&r.autoWidth===0&&r.autoHeight===0)try{const d=await m(a);if(e!==this.rebuildToken||!this.isConnected)return;const v=d.naturalWidth||d.width,E=d.naturalHeight||d.height;v>0&&E>0&&(u={...r,width:v,height:E})}catch{}this.syncContainerSize(u);const S=t(this.getAttribute("brush-size"),u.width,u.height,g.brushSize);this.container.replaceChildren(),this.instance?.destroy(),this.instance=void 0,this.lastErrorMessage=null,this.instance=new C(this.container,{width:u.width,height:u.height,percentToFinish:n,brushSrc:h,brushSize:S,imageMaskSrc:a,imageBackgroundSrc:c,onProgress:d=>{this.dispatchEvent(new CustomEvent("progress",{detail:{percent:d},bubbles:!0,composed:!0}))},onComplete:()=>{this.dispatchEvent(new CustomEvent("complete",{detail:{percent:100},bubbles:!0,composed:!0}))}}),this.instance.init().catch(d=>{const v=d instanceof Error?d.message:"ScratchReveal: init failed";this.renderError(v)}),this.updateResizeObserver(u)}renderError(e){this.instance?.destroy(),this.instance=void 0,this.renderStatus(e,"error"),this.lastErrorMessage!==e&&(this.lastErrorMessage=e,this.dispatchEvent(new CustomEvent("error",{detail:{message:e},bubbles:!0,composed:!0})))}renderWaiting(e){const r=`ScratchReveal: waiting for required attribute(s): ${e.join(", ")}`;this.renderStatus(r,"waiting")}renderStatus(e,r){if(this.container.replaceChildren(),this.instance?.destroy(),this.instance=void 0,!this.hasAttribute("show-status"))return;const n=document.createElement("div");n.className=`sr__status sr__status--${r}`,n.textContent=e,n.setAttribute("role",r==="error"?"alert":"status"),this.container.appendChild(n)}resolveSize(){const e=this.hasAttribute("width"),r=this.hasAttribute("height"),n=this.getBoundingClientRect(),h=Math.round(n.width),a=Math.round(n.height),c=Number(this.getAttribute("width")),p=Number(this.getAttribute("height")),u=e&&Number.isFinite(c)?c:h||g.width,S=r&&Number.isFinite(p)?p:a||g.height;return{hasWidthAttr:e,hasHeightAttr:r,width:u,height:S,autoWidth:h,autoHeight:a}}resolvePercentToFinish(){const e=Number(this.getAttribute("complete-percent"));return Number.isFinite(e)?e:g.percentToFinish}resolveSources(){return{brushSrc:(this.getAttribute("brush-src")??"").trim(),imageMaskSrc:(this.getAttribute("mask-src")??"").trim(),imageBackgroundSrc:(this.getAttribute("background-src")??"").trim()}}syncContainerSize(e){e.hasWidthAttr?this.container.style.width=`${e.width}px`:this.container.style.width="100%",e.hasHeightAttr?this.container.style.height=`${e.height}px`:this.container.style.height="100%"}updateResizeObserver(e){(!e.hasWidthAttr||!e.hasHeightAttr)&&"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 n=this.getBoundingClientRect(),h=Math.round(n.width),a=Math.round(n.height);this.instance?.resize(h,a);const c=t(this.getAttribute("brush-size"),h,a,g.brushSize);this.instance?.setBrushSize(c)}),this.resizeObserver.observe(this)):(this.resizeObserver?.disconnect(),this.resizeObserver=void 0)}}customElements.define(o,s)}function R(o){_(),o.config.globalProperties.$scratchReveal=!0}l.installScratchReveal=R,l.registerScratchRevealElement=_,l.scratchRevealCssText=z,Object.defineProperty(l,Symbol.toStringTag,{value:"Module"})}));
|
|
61
|
+
`,z=S;function k(o){return"adoptedStyleSheets"in o}function C(o){if(typeof CSSStyleSheet>"u")return null;try{const t=new CSSStyleSheet;return t.replaceSync(o),t}catch{return null}}const _=C(S),g={width:300,height:300,brushSrc:"",imageMaskSrc:"",imageBackgroundSrc:"",brushSize:0,percentToFinish:60};class x{config;ctx;container;_canvas;dpr=1;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,s={}){if(this.config={...g,...s},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.setCanvasScale(this.config.width,this.config.height),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,s,i]=await Promise.all([f(this.config.brushSrc),f(this.config.imageMaskSrc),f(this.config.imageBackgroundSrc)]);return this.destroyed?this:(this.brushImage=t,this.maskImage=s,this.backgroundImage=i,this.drawMask(),this.setBackground(),this.bindEvents(),this)}destroy(){this.destroyed=!0,this.removeListeners?.()}getPercent(){return this.percent}createCanvas(t,s){const i=document.createElement("canvas");return i.className="sr__canvas",i.style.width="100%",i.style.height="100%",i}setCanvasScale(t,s){const i=Math.max(1,Math.min(window.devicePixelRatio||1,3));this.dpr=i,this._canvas.width=Math.round(t*this.dpr),this._canvas.height=Math.round(s*this.dpr),this._canvas.style.width=`${t}px`,this._canvas.style.height=`${s}px`,this.ctx.setTransform(this.dpr,0,0,this.dpr,0,0)}resize(t,s){this.destroyed||this.done||t<=0||s<=0||this._canvas.width===t*this.dpr&&this._canvas.height===s*this.dpr||(this.setCanvasScale(t,s),this.percent=0,this.ctx.globalCompositeOperation="source-over",this.drawMask())}setBrushSize(t){this.destroyed||!Number.isFinite(t)||t<0||(this.brushSize=t)}bindEvents(){const s=y((n,h)=>{this.percent=this.updatePercent();const a=performance.now();(!this.lastProgressEmitMs||a-this.lastProgressEmitMs>=120)&&(this.lastProgressEmitMs=a,this.config.onProgress?.(this.percent)),this.finish(n,h)}),i=y(n=>{(n.buttons&1)!==0&&(this.updatePosition(n),this.scratch(),s(n,i))}),e=n=>{try{this._canvas.releasePointerCapture(n.pointerId)}catch{}this._canvas.removeEventListener("pointermove",i),this.percent=this.updatePercent(),this.config.onProgress?.(this.percent),this.finish(n,i)},r=n=>{n.preventDefault(),this.updatePosition(n),this.scratch(),this._canvas.setPointerCapture(n.pointerId),this._canvas.addEventListener("pointermove",i),this.percent=this.updatePercent(),this.config.onProgress?.(this.percent),this.finish(n,i)};this._canvas.addEventListener("pointerdown",r),this._canvas.addEventListener("pointerup",e),this._canvas.addEventListener("pointercancel",e),this.removeListeners=()=>{this._canvas.removeEventListener("pointerdown",r),this._canvas.removeEventListener("pointerup",e),this._canvas.removeEventListener("pointercancel",e),this._canvas.removeEventListener("pointermove",i)}}updatePosition(t){const s=this._canvas.getBoundingClientRect(),i=s.width?this._canvas.width/s.width:1,e=s.height?this._canvas.height/s.height:1,r=(t.clientX-s.left)*i/this.dpr,n=(t.clientY-s.top)*e/this.dpr;this.brush.updateMousePosition(r,n)}drawMask(){this.maskImage&&(this.ctx.globalCompositeOperation="source-over",this.ctx.clearRect(0,0,this._canvas.width/this.dpr,this._canvas.height/this.dpr),this.ctx.drawImage(this.maskImage,0,0,this._canvas.width/this.dpr,this._canvas.height/this.dpr))}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 s=this.ctx.getImageData(0,0,this._canvas.width,this._canvas.height).data;let i=0;for(let e=3;e<s.length;e+=4)s[e]===0&&i++;return i/(this._canvas.width*this._canvas.height)*100}finish(t,s){if(!this.done&&this.percent>this.config.percentToFinish&&(this.done=!0,this._canvas.style.pointerEvents="none",this.config.onComplete?.(),this.playCompleteEffect(),t&&s)){try{this._canvas.releasePointerCapture(t.pointerId)}catch{}this._canvas.removeEventListener("pointermove",s)}}playCompleteEffect(){if(this.destroyed||this.completing)return;this.completing=!0;const t=350,s="ease-out";this._canvas.style.transition=`opacity ${t}ms ${s}`,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.dpr,this._canvas.height/this.dpr)}}function E(o="scratch-reveal"){if(typeof window>"u"||!("customElements"in window)||customElements.get(o))return;function t(i,e,r,n){if(!i)return n;const h=i.trim();if(!h)return n;const a=Number.parseFloat(h.endsWith("%")?h.slice(0,-1):h.endsWith("px")?h.slice(0,-2):h);if(!Number.isFinite(a))return n;if(h.endsWith("px"))return Math.max(0,a);const u=Math.min(e,r);return Math.max(0,u*a/100)}class s extends HTMLElement{instance;container;styleEl=null;rebuildScheduled=!1;rebuildToken=0;resizeObserver;lastErrorMessage=null;static get observedAttributes(){return["width","height","complete-percent","brush-src","brush-size","mask-src","background-src","show-status"]}constructor(){super();const e=this.attachShadow({mode:"open"});k(e)&&_?e.adoptedStyleSheets=[_]:(this.styleEl=document.createElement("style"),this.styleEl.textContent=S,e.append(this.styleEl)),this.container=document.createElement("div"),this.container.className="sr",e.append(this.container)}connectedCallback(){this.scheduleRebuild()}disconnectedCallback(){this.instance?.destroy(),this.instance=void 0,this.resizeObserver?.disconnect(),this.resizeObserver=void 0}attributeChangedCallback(e,r,n){if(r!==n){if(e==="brush-size"&&this.instance){const{width:h,height:a}=this.resolveSize(),u=t(this.getAttribute("brush-size"),h,a,g.brushSize);this.instance.setBrushSize(u);return}if((e==="width"||e==="height")&&this.instance){const h=this.resolveSize();this.syncContainerSize(h),this.instance.resize(h.width,h.height);const a=t(this.getAttribute("brush-size"),h.width,h.height,g.brushSize);this.instance.setBrushSize(a),this.updateResizeObserver(h);return}this.scheduleRebuild()}}scheduleRebuild(){this.rebuildScheduled||(this.rebuildScheduled=!0,queueMicrotask(()=>{this.rebuildScheduled=!1,this.isConnected&&this.rebuild()}))}async rebuild(){const e=++this.rebuildToken,r=this.resolveSize(),n=this.resolvePercentToFinish(),{brushSrc:h,imageMaskSrc:a,imageBackgroundSrc:u}=this.resolveSources(),p=[];if(h||p.push("brush-src"),a||p.push("mask-src"),u||p.push("background-src"),p.length){this.syncContainerSize(r),this.renderWaiting(p),this.updateResizeObserver(r);return}let d=r;if(!r.hasWidthAttr&&!r.hasHeightAttr&&r.autoWidth===0&&r.autoHeight===0)try{const c=await f(a);if(e!==this.rebuildToken||!this.isConnected)return;const b=c.naturalWidth||c.width,v=c.naturalHeight||c.height;b>0&&v>0&&(d={...r,width:b,height:v})}catch{}if(!r.hasWidthAttr&&!r.hasHeightAttr){const c=this.getBoundingClientRect(),b=Math.round(c.width),v=Math.round(c.height);b>0&&v>0&&(d={...d,width:b,height:v})}this.syncContainerSize(d);const w=t(this.getAttribute("brush-size"),d.width,d.height,g.brushSize);this.container.replaceChildren(),this.instance?.destroy(),this.instance=void 0,this.lastErrorMessage=null,this.instance=new x(this.container,{width:d.width,height:d.height,percentToFinish:n,brushSrc:h,brushSize:w,imageMaskSrc:a,imageBackgroundSrc:u,onProgress:c=>{this.dispatchEvent(new CustomEvent("progress",{detail:{percent:c},bubbles:!0,composed:!0}))},onComplete:()=>{this.dispatchEvent(new CustomEvent("complete",{detail:{percent:100},bubbles:!0,composed:!0}))}}),this.instance.init().catch(c=>{const b=c instanceof Error?c.message:"ScratchReveal: init failed";this.renderError(b)}),this.updateResizeObserver(d)}renderError(e){this.instance?.destroy(),this.instance=void 0,this.renderStatus(e,"error"),this.lastErrorMessage!==e&&(this.lastErrorMessage=e,this.dispatchEvent(new CustomEvent("error",{detail:{message:e},bubbles:!0,composed:!0})))}renderWaiting(e){const r=`ScratchReveal: waiting for required attribute(s): ${e.join(", ")}`;this.renderStatus(r,"waiting")}renderStatus(e,r){if(this.container.replaceChildren(),this.instance?.destroy(),this.instance=void 0,!this.hasAttribute("show-status"))return;const n=document.createElement("div");n.className=`sr__status sr__status--${r}`,n.textContent=e,n.setAttribute("role",r==="error"?"alert":"status"),this.container.appendChild(n)}resolveSize(){const e=this.hasAttribute("width"),r=this.hasAttribute("height"),n=this.getBoundingClientRect(),h=Math.round(n.width),a=Math.round(n.height),u=Number(this.getAttribute("width")),p=Number(this.getAttribute("height")),d=e&&Number.isFinite(u)?u:h||g.width,w=r&&Number.isFinite(p)?p:a||g.height;return{hasWidthAttr:e,hasHeightAttr:r,width:d,height:w,autoWidth:h,autoHeight:a}}resolvePercentToFinish(){const e=Number(this.getAttribute("complete-percent"));return Number.isFinite(e)?e:g.percentToFinish}resolveSources(){return{brushSrc:(this.getAttribute("brush-src")??"").trim(),imageMaskSrc:(this.getAttribute("mask-src")??"").trim(),imageBackgroundSrc:(this.getAttribute("background-src")??"").trim()}}syncContainerSize(e){e.hasWidthAttr?this.container.style.width=`${e.width}px`:this.container.style.width="100%",e.hasHeightAttr?this.container.style.height=`${e.height}px`:this.container.style.height="100%"}updateResizeObserver(e){(!e.hasWidthAttr||!e.hasHeightAttr)&&"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 n=this.getBoundingClientRect(),h=Math.round(n.width),a=Math.round(n.height);this.instance?.resize(h,a);const u=t(this.getAttribute("brush-size"),h,a,g.brushSize);this.instance?.setBrushSize(u)}),this.resizeObserver.observe(this)):(this.resizeObserver?.disconnect(),this.resizeObserver=void 0)}}customElements.define(o,s)}function R(o){E(),o.config.globalProperties.$scratchReveal=!0}l.installScratchReveal=R,l.registerScratchRevealElement=E,l.scratchRevealCssText=z,Object.defineProperty(l,Symbol.toStringTag,{value:"Module"})}));
|
package/package.json
CHANGED