scratch-reveal 1.3.0-dev.1 → 1.3.0-dev.3
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 +30 -20
- 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 S{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,h=s*(t.height/t.width);this.ctx.drawImage(t,-(e/2),-(h/2),e,h)}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 S{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,h=s*(t.height/t.width);this.ctx.drawImage(t,-(e/2),-(h/2),e,h)}else this.ctx.drawImage(t,-(t.width/2),-(t.height/2));this.ctx.restore()}}function b(c){return new Promise((t,s)=>{const i=new Image;i.crossOrigin="anonymous",i.onload=()=>t(i),i.onerror=()=>s(new Error(`Image ${c} failed to load`)),i.src=c})}function m(c){let t=0;return((...i)=>{t||(t=requestAnimationFrame(()=>{t=0,c(...i)}))})}const p=`.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
|
-
`,w=b;function _(c){return"adoptedStyleSheets"in c}function E(c){if(typeof CSSStyleSheet>"u")return null;try{const t=new CSSStyleSheet;return t.replaceSync(c),t}catch{return null}}const v=E(b),d={width:300,height:300,brushSrc:"",imageMaskSrc:"",imageBackgroundSrc:"",brushSize:0,percentToFinish:60};class y{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={...d,...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 S(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=m((n,r)=>{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,r)}),i=m(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)},h=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",h),this._canvas.addEventListener("pointerup",e),this._canvas.addEventListener("pointercancel",e),this.removeListeners=()=>{this._canvas.removeEventListener("pointerdown",h),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,h=(t.clientX-s.left)*i/this.dpr,n=(t.clientY-s.top)*e/this.dpr;this.brush.updateMousePosition(h,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 f(c="scratch-reveal"){if(typeof window>"u"||!("customElements"in window)||customElements.get(c))return;function t(i,e,h,n){if(!i)return n;const r=i.trim();if(!r)return n;const a=Number.parseFloat(r.endsWith("%")?r.slice(0,-1):r.endsWith("px")?r.slice(0,-2):r);if(!Number.isFinite(a))return n;if(r.endsWith("px"))return Math.max(0,a);const o=Math.min(e,h);return Math.max(0,o*a/100)}class s 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","show-status"]}constructor(){super();const e=this.attachShadow({mode:"open"});_(e)&&v?e.adoptedStyleSheets=[v]:(this.styleEl=document.createElement("style"),this.styleEl.textContent=b,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,h,n){if(h!==n){if(e==="brush-size"&&this.instance){const{width:r,height:a}=this.resolveSize(),o=t(this.getAttribute("brush-size"),r,a,d.brushSize);this.instance.setBrushSize(o);return}if((e==="width"||e==="height")&&this.instance){const r=this.resolveSize();this.syncContainerSize(r),this.instance.resize(r.width,r.height);const a=t(this.getAttribute("brush-size"),r.width,r.height,d.brushSize);this.instance.setBrushSize(a),this.updateResizeObserver(r);return}this.scheduleRebuild()}}scheduleRebuild(){this.rebuildScheduled||(this.rebuildScheduled=!0,queueMicrotask(()=>{this.rebuildScheduled=!1,this.isConnected&&this.rebuild()}))}rebuild(){const e=this.resolveSize(),h=this.resolvePercentToFinish(),{brushSrc:n,imageMaskSrc:r,imageBackgroundSrc:a}=this.resolveSources(),o=t(this.getAttribute("brush-size"),e.width,e.height,d.brushSize),u=[];if(n||u.push("brush-src"),r||u.push("mask-src"),a||u.push("background-src"),this.syncContainerSize(e),u.length){this.renderWaiting(u),this.updateResizeObserver(e);return}this.container.replaceChildren(),this.instance?.destroy(),this.instance=void 0,this.lastErrorMessage=null,this.instance=new y(this.container,{width:e.width,height:e.height,percentToFinish:h,brushSrc:n,brushSize:o,imageMaskSrc:r,imageBackgroundSrc:a,onProgress:l=>{this.dispatchEvent(new CustomEvent("progress",{detail:{percent:l},bubbles:!0,composed:!0}))},onComplete:()=>{this.dispatchEvent(new CustomEvent("complete",{detail:{percent:100},bubbles:!0,composed:!0}))}}),this.instance.init().catch(l=>{const g=l instanceof Error?l.message:"ScratchReveal: init failed";this.renderError(g)}),this.updateResizeObserver(e)}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 h=`ScratchReveal: waiting for required attribute(s): ${e.join(", ")}`;this.renderStatus(h,"waiting")}renderStatus(e,h){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--${h}`,n.textContent=e,n.setAttribute("role",h==="error"?"alert":"status"),this.container.appendChild(n)}resolveSize(){const e=this.hasAttribute("width"),h=this.hasAttribute("height"),n=this.getBoundingClientRect(),r=Math.round(n.width),a=Math.round(n.height),o=Number(this.getAttribute("width")),u=Number(this.getAttribute("height")),l=e&&Number.isFinite(o)?o:r||d.width,g=h&&Number.isFinite(u)?u:a||d.height;return{hasWidthAttr:e,hasHeightAttr:h,width:l,height:g}}resolvePercentToFinish(){const e=Number(this.getAttribute("complete-percent"));return Number.isFinite(e)?e:d.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(),r=Math.round(n.width),a=Math.round(n.height);this.instance?.resize(r,a);const o=t(this.getAttribute("brush-size"),r,a,d.brushSize);this.instance?.setBrushSize(o)}),this.resizeObserver.observe(this)):(this.resizeObserver?.disconnect(),this.resizeObserver=void 0)}}customElements.define(c,s)}function z(c){f(),c.config.globalProperties.$scratchReveal=!0}exports.installScratchReveal=z;exports.registerScratchRevealElement=f;exports.scratchRevealCssText=w;
|
|
61
|
+
`,w=p;function _(c){return"adoptedStyleSheets"in c}function y(c){if(typeof CSSStyleSheet>"u")return null;try{const t=new CSSStyleSheet;return t.replaceSync(c),t}catch{return null}}const v=y(p),u={width:300,height:300,brushSrc:"",imageMaskSrc:"",imageBackgroundSrc:"",brushSize:0,percentToFinish:60};class E{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={...u,...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 S(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([b(this.config.brushSrc),b(this.config.imageMaskSrc),b(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=m((n,r)=>{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,r)}),i=m(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)},h=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",h),this._canvas.addEventListener("pointerup",e),this._canvas.addEventListener("pointercancel",e),this.removeListeners=()=>{this._canvas.removeEventListener("pointerdown",h),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,h=(t.clientX-s.left)*i/this.dpr,n=(t.clientY-s.top)*e/this.dpr;this.brush.updateMousePosition(h,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 f(c="scratch-reveal"){if(typeof window>"u"||!("customElements"in window)||customElements.get(c))return;function t(i,e,h,n){if(!i)return n;const r=i.trim();if(!r)return n;const a=Number.parseFloat(r.endsWith("%")?r.slice(0,-1):r.endsWith("px")?r.slice(0,-2):r);if(!Number.isFinite(a))return n;if(r.endsWith("px"))return Math.max(0,a);const o=Math.min(e,h);return Math.max(0,o*a/100)}class s 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","show-status"]}constructor(){super();const e=this.attachShadow({mode:"open"});_(e)&&v?e.adoptedStyleSheets=[v]:(this.styleEl=document.createElement("style"),this.styleEl.textContent=p,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,h,n){if(h!==n){if(e==="brush-size"&&this.instance){const{width:r,height:a}=this.resolveSize(),o=t(this.getAttribute("brush-size"),r,a,u.brushSize);this.instance.setBrushSize(o);return}if((e==="width"||e==="height")&&this.instance){const r=this.resolveSize();this.syncContainerSize(r),this.instance.resize(r.width,r.height);const a=t(this.getAttribute("brush-size"),r.width,r.height,u.brushSize);this.instance.setBrushSize(a),this.updateResizeObserver(r);return}this.scheduleRebuild()}}scheduleRebuild(){this.rebuildScheduled||(this.rebuildScheduled=!0,queueMicrotask(()=>{this.rebuildScheduled=!1,this.isConnected&&this.rebuild()}))}rebuild(){const e=this.resolveSize(),h=this.resolvePercentToFinish(),{brushSrc:n,imageMaskSrc:r,imageBackgroundSrc:a}=this.resolveSources(),o=[];if(n||o.push("brush-src"),r||o.push("mask-src"),a||o.push("background-src"),!e.hasWidthAttr&&!e.hasHeightAttr&&e.autoWidth===0&&e.autoHeight===0){this.syncContainerSize(e),this.instance||this.renderWaiting(["size"]),this.updateResizeObserver(e);return}if(o.length){this.syncContainerSize(e),this.renderWaiting(o),this.updateResizeObserver(e);return}this.syncContainerSize(e);const l=t(this.getAttribute("brush-size"),e.width,e.height,u.brushSize);this.container.replaceChildren(),this.instance?.destroy(),this.instance=void 0,this.lastErrorMessage=null,this.instance=new E(this.container,{width:e.width,height:e.height,percentToFinish:h,brushSrc:n,brushSize:l,imageMaskSrc:r,imageBackgroundSrc:a,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 g=d instanceof Error?d.message:"ScratchReveal: init failed";this.renderError(g)}),this.updateResizeObserver(e)}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 h=`ScratchReveal: waiting for required attribute(s): ${e.join(", ")}`;this.renderStatus(h,"waiting")}renderStatus(e,h){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--${h}`,n.textContent=e,n.setAttribute("role",h==="error"?"alert":"status"),this.container.appendChild(n)}resolveSize(){const e=this.hasAttribute("width"),h=this.hasAttribute("height"),n=this.getBoundingClientRect(),r=Math.round(n.width),a=Math.round(n.height),o=Number(this.getAttribute("width")),l=Number(this.getAttribute("height")),d=e?Number.isFinite(o)?o:u.width:r,g=h?Number.isFinite(l)?l:u.height:a;return{hasWidthAttr:e,hasHeightAttr:h,width:d,height:g,autoWidth:r,autoHeight:a}}resolvePercentToFinish(){const e=Number(this.getAttribute("complete-percent"));return Number.isFinite(e)?e:u.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(),r=Math.round(n.width),a=Math.round(n.height);if(!this.instance&&r>0&&a>0){this.scheduleRebuild();return}this.instance?.resize(r,a);const o=t(this.getAttribute("brush-size"),r,a,u.brushSize);this.instance?.setBrushSize(o)}),this.resizeObserver.observe(this)):(this.resizeObserver?.disconnect(),this.resizeObserver=void 0)}}customElements.define(c,s)}function z(c){f(),c.config.globalProperties.$scratchReveal=!0}exports.installScratchReveal=z;exports.registerScratchRevealElement=f;exports.scratchRevealCssText=w;
|
package/dist/index.es.js
CHANGED
|
@@ -107,7 +107,7 @@ function w(o) {
|
|
|
107
107
|
return null;
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
|
-
const v = w(b),
|
|
110
|
+
const v = w(b), u = {
|
|
111
111
|
width: 300,
|
|
112
112
|
height: 300,
|
|
113
113
|
brushSrc: "",
|
|
@@ -138,7 +138,7 @@ class _ {
|
|
|
138
138
|
return this._canvas;
|
|
139
139
|
}
|
|
140
140
|
constructor(t, s = {}) {
|
|
141
|
-
if (this.config = { ...
|
|
141
|
+
if (this.config = { ...u, ...s }, this.container = typeof t == "string" ? document.querySelector(t) : t, !this.container)
|
|
142
142
|
throw new Error("ScratchReveal: container not found");
|
|
143
143
|
this._canvas = this.createCanvas(this.config.width, this.config.height), this.ctx = this._canvas.getContext("2d", {
|
|
144
144
|
willReadFrequently: !0
|
|
@@ -305,7 +305,7 @@ function E(o = "scratch-reveal") {
|
|
|
305
305
|
this.getAttribute("brush-size"),
|
|
306
306
|
r,
|
|
307
307
|
a,
|
|
308
|
-
|
|
308
|
+
u.brushSize
|
|
309
309
|
);
|
|
310
310
|
this.instance.setBrushSize(c);
|
|
311
311
|
return;
|
|
@@ -317,7 +317,7 @@ function E(o = "scratch-reveal") {
|
|
|
317
317
|
this.getAttribute("brush-size"),
|
|
318
318
|
r.width,
|
|
319
319
|
r.height,
|
|
320
|
-
|
|
320
|
+
u.brushSize
|
|
321
321
|
);
|
|
322
322
|
this.instance.setBrushSize(a), this.updateResizeObserver(r);
|
|
323
323
|
return;
|
|
@@ -331,28 +331,34 @@ function E(o = "scratch-reveal") {
|
|
|
331
331
|
}));
|
|
332
332
|
}
|
|
333
333
|
rebuild() {
|
|
334
|
-
const e = this.resolveSize(), h = this.resolvePercentToFinish(), { brushSrc: n, imageMaskSrc: r, imageBackgroundSrc: a } = this.resolveSources(), c =
|
|
334
|
+
const e = this.resolveSize(), h = this.resolvePercentToFinish(), { brushSrc: n, imageMaskSrc: r, imageBackgroundSrc: a } = this.resolveSources(), c = [];
|
|
335
|
+
if (n || c.push("brush-src"), r || c.push("mask-src"), a || c.push("background-src"), !e.hasWidthAttr && !e.hasHeightAttr && e.autoWidth === 0 && e.autoHeight === 0) {
|
|
336
|
+
this.syncContainerSize(e), this.instance || this.renderWaiting(["size"]), this.updateResizeObserver(e);
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
if (c.length) {
|
|
340
|
+
this.syncContainerSize(e), this.renderWaiting(c), this.updateResizeObserver(e);
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
this.syncContainerSize(e);
|
|
344
|
+
const l = t(
|
|
335
345
|
this.getAttribute("brush-size"),
|
|
336
346
|
e.width,
|
|
337
347
|
e.height,
|
|
338
|
-
|
|
339
|
-
)
|
|
340
|
-
if (n || u.push("brush-src"), r || u.push("mask-src"), a || u.push("background-src"), this.syncContainerSize(e), u.length) {
|
|
341
|
-
this.renderWaiting(u), this.updateResizeObserver(e);
|
|
342
|
-
return;
|
|
343
|
-
}
|
|
348
|
+
u.brushSize
|
|
349
|
+
);
|
|
344
350
|
this.container.replaceChildren(), this.instance?.destroy(), this.instance = void 0, this.lastErrorMessage = null, this.instance = new _(this.container, {
|
|
345
351
|
width: e.width,
|
|
346
352
|
height: e.height,
|
|
347
353
|
percentToFinish: h,
|
|
348
354
|
brushSrc: n,
|
|
349
|
-
brushSize:
|
|
355
|
+
brushSize: l,
|
|
350
356
|
imageMaskSrc: r,
|
|
351
357
|
imageBackgroundSrc: a,
|
|
352
|
-
onProgress: (
|
|
358
|
+
onProgress: (d) => {
|
|
353
359
|
this.dispatchEvent(
|
|
354
360
|
new CustomEvent("progress", {
|
|
355
|
-
detail: { percent:
|
|
361
|
+
detail: { percent: d },
|
|
356
362
|
bubbles: !0,
|
|
357
363
|
composed: !0
|
|
358
364
|
})
|
|
@@ -367,8 +373,8 @@ function E(o = "scratch-reveal") {
|
|
|
367
373
|
})
|
|
368
374
|
);
|
|
369
375
|
}
|
|
370
|
-
}), this.instance.init().catch((
|
|
371
|
-
const g =
|
|
376
|
+
}), this.instance.init().catch((d) => {
|
|
377
|
+
const g = d instanceof Error ? d.message : "ScratchReveal: init failed";
|
|
372
378
|
this.renderError(g);
|
|
373
379
|
}), this.updateResizeObserver(e);
|
|
374
380
|
}
|
|
@@ -391,12 +397,12 @@ function E(o = "scratch-reveal") {
|
|
|
391
397
|
n.className = `sr__status sr__status--${h}`, n.textContent = e, n.setAttribute("role", h === "error" ? "alert" : "status"), this.container.appendChild(n);
|
|
392
398
|
}
|
|
393
399
|
resolveSize() {
|
|
394
|
-
const e = this.hasAttribute("width"), h = this.hasAttribute("height"), n = this.getBoundingClientRect(), r = Math.round(n.width), a = Math.round(n.height), c = Number(this.getAttribute("width")),
|
|
395
|
-
return { hasWidthAttr: e, hasHeightAttr: h, width:
|
|
400
|
+
const e = this.hasAttribute("width"), h = this.hasAttribute("height"), n = this.getBoundingClientRect(), r = Math.round(n.width), a = Math.round(n.height), c = Number(this.getAttribute("width")), l = Number(this.getAttribute("height")), d = e ? Number.isFinite(c) ? c : u.width : r, g = h ? Number.isFinite(l) ? l : u.height : a;
|
|
401
|
+
return { hasWidthAttr: e, hasHeightAttr: h, width: d, height: g, autoWidth: r, autoHeight: a };
|
|
396
402
|
}
|
|
397
403
|
resolvePercentToFinish() {
|
|
398
404
|
const e = Number(this.getAttribute("complete-percent"));
|
|
399
|
-
return Number.isFinite(e) ? e :
|
|
405
|
+
return Number.isFinite(e) ? e : u.percentToFinish;
|
|
400
406
|
}
|
|
401
407
|
resolveSources() {
|
|
402
408
|
return {
|
|
@@ -415,12 +421,16 @@ function E(o = "scratch-reveal") {
|
|
|
415
421
|
return;
|
|
416
422
|
}
|
|
417
423
|
const n = this.getBoundingClientRect(), r = Math.round(n.width), a = Math.round(n.height);
|
|
424
|
+
if (!this.instance && r > 0 && a > 0) {
|
|
425
|
+
this.scheduleRebuild();
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
418
428
|
this.instance?.resize(r, a);
|
|
419
429
|
const c = t(
|
|
420
430
|
this.getAttribute("brush-size"),
|
|
421
431
|
r,
|
|
422
432
|
a,
|
|
423
|
-
|
|
433
|
+
u.brushSize
|
|
424
434
|
);
|
|
425
435
|
this.instance?.setBrushSize(c);
|
|
426
436
|
}), this.resizeObserver.observe(this)) : (this.resizeObserver?.disconnect(), this.resizeObserver = void 0);
|
package/dist/index.umd.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
(function(u,
|
|
1
|
+
(function(u,g){typeof exports=="object"&&typeof module<"u"?g(exports):typeof define=="function"&&define.amd?define(["exports"],g):(u=typeof globalThis<"u"?globalThis:u||self,g(u.ScratchReveal={}))})(this,(function(u){"use strict";class g{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,h=s*(t.height/t.width);this.ctx.drawImage(t,-(e/2),-(h/2),e,h)}else this.ctx.drawImage(t,-(t.width/2),-(t.height/2));this.ctx.restore()}}function b(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 f(o){let t=0;return((...i)=>{t||(t=requestAnimationFrame(()=>{t=0,o(...i)}))})}const m=`.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
|
-
`,_=m;function y(o){return"adoptedStyleSheets"in o}function E(o){if(typeof CSSStyleSheet>"u")return null;try{const t=new CSSStyleSheet;return t.replaceSync(o),t}catch{return null}}const S=E(m),d={width:300,height:300,brushSrc:"",imageMaskSrc:"",imageBackgroundSrc:"",brushSize:0,percentToFinish:60};class z{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={...d,...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 p(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([b(this.config.brushSrc),b(this.config.imageMaskSrc),b(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=f((n,r)=>{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,r)}),i=f(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)},h=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",h),this._canvas.addEventListener("pointerup",e),this._canvas.addEventListener("pointercancel",e),this.removeListeners=()=>{this._canvas.removeEventListener("pointerdown",h),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,h=(t.clientX-s.left)*i/this.dpr,n=(t.clientY-s.top)*e/this.dpr;this.brush.updateMousePosition(h,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 w(o="scratch-reveal"){if(typeof window>"u"||!("customElements"in window)||customElements.get(o))return;function t(i,e,h,n){if(!i)return n;const r=i.trim();if(!r)return n;const a=Number.parseFloat(r.endsWith("%")?r.slice(0,-1):r.endsWith("px")?r.slice(0,-2):r);if(!Number.isFinite(a))return n;if(r.endsWith("px"))return Math.max(0,a);const c=Math.min(e,h);return Math.max(0,c*a/100)}class s 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","show-status"]}constructor(){super();const e=this.attachShadow({mode:"open"});y(e)&&S?e.adoptedStyleSheets=[S]:(this.styleEl=document.createElement("style"),this.styleEl.textContent=m,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,h,n){if(h!==n){if(e==="brush-size"&&this.instance){const{width:r,height:a}=this.resolveSize(),c=t(this.getAttribute("brush-size"),r,a,d.brushSize);this.instance.setBrushSize(c);return}if((e==="width"||e==="height")&&this.instance){const r=this.resolveSize();this.syncContainerSize(r),this.instance.resize(r.width,r.height);const a=t(this.getAttribute("brush-size"),r.width,r.height,d.brushSize);this.instance.setBrushSize(a),this.updateResizeObserver(r);return}this.scheduleRebuild()}}scheduleRebuild(){this.rebuildScheduled||(this.rebuildScheduled=!0,queueMicrotask(()=>{this.rebuildScheduled=!1,this.isConnected&&this.rebuild()}))}rebuild(){const e=this.resolveSize(),h=this.resolvePercentToFinish(),{brushSrc:n,imageMaskSrc:r,imageBackgroundSrc:a}=this.resolveSources(),c=t(this.getAttribute("brush-size"),e.width,e.height,d.brushSize),l=[];if(n||l.push("brush-src"),r||l.push("mask-src"),a||l.push("background-src"),this.syncContainerSize(e),l.length){this.renderWaiting(l),this.updateResizeObserver(e);return}this.container.replaceChildren(),this.instance?.destroy(),this.instance=void 0,this.lastErrorMessage=null,this.instance=new z(this.container,{width:e.width,height:e.height,percentToFinish:h,brushSrc:n,brushSize:c,imageMaskSrc:r,imageBackgroundSrc:a,onProgress:g=>{this.dispatchEvent(new CustomEvent("progress",{detail:{percent:g},bubbles:!0,composed:!0}))},onComplete:()=>{this.dispatchEvent(new CustomEvent("complete",{detail:{percent:100},bubbles:!0,composed:!0}))}}),this.instance.init().catch(g=>{const v=g instanceof Error?g.message:"ScratchReveal: init failed";this.renderError(v)}),this.updateResizeObserver(e)}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 h=`ScratchReveal: waiting for required attribute(s): ${e.join(", ")}`;this.renderStatus(h,"waiting")}renderStatus(e,h){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--${h}`,n.textContent=e,n.setAttribute("role",h==="error"?"alert":"status"),this.container.appendChild(n)}resolveSize(){const e=this.hasAttribute("width"),h=this.hasAttribute("height"),n=this.getBoundingClientRect(),r=Math.round(n.width),a=Math.round(n.height),c=Number(this.getAttribute("width")),l=Number(this.getAttribute("height")),g=e&&Number.isFinite(c)?c:r||d.width,v=h&&Number.isFinite(l)?l:a||d.height;return{hasWidthAttr:e,hasHeightAttr:h,width:g,height:v}}resolvePercentToFinish(){const e=Number(this.getAttribute("complete-percent"));return Number.isFinite(e)?e:d.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(),r=Math.round(n.width),a=Math.round(n.height);this.instance?.resize(r,a);const c=t(this.getAttribute("brush-size"),r,a,d.brushSize);this.instance?.setBrushSize(c)}),this.resizeObserver.observe(this)):(this.resizeObserver?.disconnect(),this.resizeObserver=void 0)}}customElements.define(o,s)}function k(o){w(),o.config.globalProperties.$scratchReveal=!0}u.installScratchReveal=k,u.registerScratchRevealElement=w,u.scratchRevealCssText=_,Object.defineProperty(u,Symbol.toStringTag,{value:"Module"})}));
|
|
61
|
+
`,y=m;function _(o){return"adoptedStyleSheets"in o}function E(o){if(typeof CSSStyleSheet>"u")return null;try{const t=new CSSStyleSheet;return t.replaceSync(o),t}catch{return null}}const S=E(m),d={width:300,height:300,brushSrc:"",imageMaskSrc:"",imageBackgroundSrc:"",brushSize:0,percentToFinish:60};class z{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={...d,...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 g(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([b(this.config.brushSrc),b(this.config.imageMaskSrc),b(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=f((n,r)=>{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,r)}),i=f(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)},h=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",h),this._canvas.addEventListener("pointerup",e),this._canvas.addEventListener("pointercancel",e),this.removeListeners=()=>{this._canvas.removeEventListener("pointerdown",h),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,h=(t.clientX-s.left)*i/this.dpr,n=(t.clientY-s.top)*e/this.dpr;this.brush.updateMousePosition(h,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 w(o="scratch-reveal"){if(typeof window>"u"||!("customElements"in window)||customElements.get(o))return;function t(i,e,h,n){if(!i)return n;const r=i.trim();if(!r)return n;const a=Number.parseFloat(r.endsWith("%")?r.slice(0,-1):r.endsWith("px")?r.slice(0,-2):r);if(!Number.isFinite(a))return n;if(r.endsWith("px"))return Math.max(0,a);const c=Math.min(e,h);return Math.max(0,c*a/100)}class s 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","show-status"]}constructor(){super();const e=this.attachShadow({mode:"open"});_(e)&&S?e.adoptedStyleSheets=[S]:(this.styleEl=document.createElement("style"),this.styleEl.textContent=m,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,h,n){if(h!==n){if(e==="brush-size"&&this.instance){const{width:r,height:a}=this.resolveSize(),c=t(this.getAttribute("brush-size"),r,a,d.brushSize);this.instance.setBrushSize(c);return}if((e==="width"||e==="height")&&this.instance){const r=this.resolveSize();this.syncContainerSize(r),this.instance.resize(r.width,r.height);const a=t(this.getAttribute("brush-size"),r.width,r.height,d.brushSize);this.instance.setBrushSize(a),this.updateResizeObserver(r);return}this.scheduleRebuild()}}scheduleRebuild(){this.rebuildScheduled||(this.rebuildScheduled=!0,queueMicrotask(()=>{this.rebuildScheduled=!1,this.isConnected&&this.rebuild()}))}rebuild(){const e=this.resolveSize(),h=this.resolvePercentToFinish(),{brushSrc:n,imageMaskSrc:r,imageBackgroundSrc:a}=this.resolveSources(),c=[];if(n||c.push("brush-src"),r||c.push("mask-src"),a||c.push("background-src"),!e.hasWidthAttr&&!e.hasHeightAttr&&e.autoWidth===0&&e.autoHeight===0){this.syncContainerSize(e),this.instance||this.renderWaiting(["size"]),this.updateResizeObserver(e);return}if(c.length){this.syncContainerSize(e),this.renderWaiting(c),this.updateResizeObserver(e);return}this.syncContainerSize(e);const p=t(this.getAttribute("brush-size"),e.width,e.height,d.brushSize);this.container.replaceChildren(),this.instance?.destroy(),this.instance=void 0,this.lastErrorMessage=null,this.instance=new z(this.container,{width:e.width,height:e.height,percentToFinish:h,brushSrc:n,brushSize:p,imageMaskSrc:r,imageBackgroundSrc:a,onProgress:l=>{this.dispatchEvent(new CustomEvent("progress",{detail:{percent:l},bubbles:!0,composed:!0}))},onComplete:()=>{this.dispatchEvent(new CustomEvent("complete",{detail:{percent:100},bubbles:!0,composed:!0}))}}),this.instance.init().catch(l=>{const v=l instanceof Error?l.message:"ScratchReveal: init failed";this.renderError(v)}),this.updateResizeObserver(e)}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 h=`ScratchReveal: waiting for required attribute(s): ${e.join(", ")}`;this.renderStatus(h,"waiting")}renderStatus(e,h){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--${h}`,n.textContent=e,n.setAttribute("role",h==="error"?"alert":"status"),this.container.appendChild(n)}resolveSize(){const e=this.hasAttribute("width"),h=this.hasAttribute("height"),n=this.getBoundingClientRect(),r=Math.round(n.width),a=Math.round(n.height),c=Number(this.getAttribute("width")),p=Number(this.getAttribute("height")),l=e?Number.isFinite(c)?c:d.width:r,v=h?Number.isFinite(p)?p:d.height:a;return{hasWidthAttr:e,hasHeightAttr:h,width:l,height:v,autoWidth:r,autoHeight:a}}resolvePercentToFinish(){const e=Number(this.getAttribute("complete-percent"));return Number.isFinite(e)?e:d.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(),r=Math.round(n.width),a=Math.round(n.height);if(!this.instance&&r>0&&a>0){this.scheduleRebuild();return}this.instance?.resize(r,a);const c=t(this.getAttribute("brush-size"),r,a,d.brushSize);this.instance?.setBrushSize(c)}),this.resizeObserver.observe(this)):(this.resizeObserver?.disconnect(),this.resizeObserver=void 0)}}customElements.define(o,s)}function k(o){w(),o.config.globalProperties.$scratchReveal=!0}u.installScratchReveal=k,u.registerScratchRevealElement=w,u.scratchRevealCssText=y,Object.defineProperty(u,Symbol.toStringTag,{value:"Module"})}));
|
package/package.json
CHANGED