@zumer/snapdom 1.0.0-pre.1747581859131 β 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -0
- package/dist/snapdom.js +1 -1
- package/dist/snapdom.min.js +3 -3
- package/dist/snapdom.min.mjs +3 -3
- package/dist/snapdom.mjs +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -141,6 +141,31 @@ All capture methods accept an `options` object:
|
|
|
141
141
|
}
|
|
142
142
|
```
|
|
143
143
|
|
|
144
|
+
|
|
145
|
+
### `preCache()` β Optional helper
|
|
146
|
+
|
|
147
|
+
The `preCache()` function can be used to load external resources (like images and fonts) in advance. It is specially useful when the element to capure is big and complex.
|
|
148
|
+
|
|
149
|
+
```js
|
|
150
|
+
import { preCache } from '@zumer/snapdom';
|
|
151
|
+
|
|
152
|
+
await preCache(document.body);
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
```js
|
|
156
|
+
import { snapdom, preCache } from './snapdom.mjs';
|
|
157
|
+
window.addEventListener('load', async () => {
|
|
158
|
+
await preCache();
|
|
159
|
+
console.log('π¦ Resources preloaded');
|
|
160
|
+
});
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**Options for `preCache()`:**
|
|
164
|
+
|
|
165
|
+
* `embedFonts` *(boolean, default: true)* β Inlines non-icon fonts during preload.
|
|
166
|
+
* `reset` *(boolean, default: false)* β Clears all existing internal caches.
|
|
167
|
+
|
|
168
|
+
|
|
144
169
|
## Features
|
|
145
170
|
|
|
146
171
|
* Captures **shadow DOM** and Web Components
|
|
@@ -156,6 +181,41 @@ All capture methods accept an `options` object:
|
|
|
156
181
|
* Iframes are not supported
|
|
157
182
|
* When WebP format is used on Safari, it will fallback to PNG rendering.
|
|
158
183
|
|
|
184
|
+
## Benchmarks
|
|
185
|
+
|
|
186
|
+
`snapDOM` is not only highly accurate β itβs **extremely fast**.
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
Latest benchmarks show significant performance improvements against other libraries:
|
|
190
|
+
|
|
191
|
+
| Scenario | vs. `modern-screenshot` | vs. `html2canvas` |
|
|
192
|
+
| -------------------------------- | :---------------------: | :---------------: |
|
|
193
|
+
| Small element (200Γ100) | 6.46Γ faster | 32.27Γ faster |
|
|
194
|
+
| Modal size (400Γ300) | 7.28Γ faster | 32.66Γ faster |
|
|
195
|
+
| Page view (1200Γ800) | 13.17Γ faster | 35.29Γ faster |
|
|
196
|
+
| Large scroll area (2000Γ1500) | 38.23Γ faster | 68.85Γ faster |
|
|
197
|
+
| Very large element (4000Γ2000) | 93.31Γ faster | 133.12Γ faster |
|
|
198
|
+
| Complex small element (200Γ100) | 3.97Γ faster | 15.23Γ faster |
|
|
199
|
+
| Complex modal (400Γ300) | 2.32Γ faster | 5.33Γ faster |
|
|
200
|
+
| Complex page (1200Γ800) | 1.62Γ faster | 1.65Γ faster |
|
|
201
|
+
| Complex large scroll (2000Γ1500) | 1.66Γ faster | 1.24Γ faster |
|
|
202
|
+
| Complex very large (4000Γ2000) | 1.52Γ faster | 1.28Γ faster |
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
### Run the benchmarks
|
|
206
|
+
|
|
207
|
+
To run these benchmarks yourself:
|
|
208
|
+
|
|
209
|
+
```sh
|
|
210
|
+
git clone https://github.com/zumerlab/snapdom.git
|
|
211
|
+
cd snapdom
|
|
212
|
+
npm install
|
|
213
|
+
npm run test:benchmark
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
They execute in **headless Chromium** using real DOM nodes.
|
|
217
|
+
|
|
218
|
+
|
|
159
219
|
## License
|
|
160
220
|
|
|
161
221
|
MIT Β© Zumerlab
|
package/dist/snapdom.js
CHANGED
package/dist/snapdom.min.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
(()=>{var S=new Map,E=new Map,$=new Map,
|
|
2
|
-
`;return o}function J(
|
|
3
|
-
`}catch{console.warn("\u274C No pude fetch CSS",a.href)}if(n&&
|
|
1
|
+
(()=>{var S=new Map,E=new Map,$=new Map,k=new Map,T=new Map,R=new WeakMap;var rt=["div","span","p","a","img","ul","li","button","input","select","textarea","label","section","article","header","footer","nav","main","aside","h1","h2","h3","h4","h5","h6","svg","path","circle","rect","line","g","table","thead","tbody","tr","td","th"];function _(){for(let t of rt)z(t)}function z(t){if(k.has(t))return k.get(t);if(new Set(["script","style","meta","link","noscript","template"]).has(t)){let s={};return k.set(t,s),s}let o=document.getElementById("snapdom-sandbox");o||(o=document.createElement("div"),o.id="snapdom-sandbox",o.style.position="absolute",o.style.left="-9999px",o.style.top="-9999px",o.style.width="0",o.style.height="0",o.style.overflow="hidden",document.body.appendChild(o));let n=document.createElement(t);n.style.all="initial",o.appendChild(n);let a=getComputedStyle(n),r={};for(let s of a)r[s]=a.getPropertyValue(s);return o.removeChild(n),k.set(t,r),r}function U(t,e,o=!1){let n=[],a=z(e);for(let[r,s]of Object.entries(t))if(!o)s&&n.push(`${r}:${s}`);else{let c=a[r];s&&s!==c&&n.push(`${r}:${s}`)}return n.sort().join(";")}function H(t){let e=new Set;return t.nodeType!==Node.ELEMENT_NODE&&t.nodeType!==Node.DOCUMENT_FRAGMENT_NODE?[]:(t.tagName&&e.add(t.tagName.toLowerCase()),typeof t.querySelectorAll=="function"&&t.querySelectorAll("*").forEach(o=>e.add(o.tagName.toLowerCase())),Array.from(e))}function K(t){let e=new Map;for(let n of t){let a=k.get(n);if(!a)continue;let r=Object.entries(a).map(([s,c])=>`${s}:${c};`).sort().join("");e.has(r)||e.set(r,[]),e.get(r).push(n)}let o="";for(let[n,a]of e.entries())o+=`${a.join(",")} { ${n} }
|
|
2
|
+
`;return o}function J(t){let e=new Set(t.values()),o=new Map,n=1;for(let a of e)o.set(a,`c${n++}`);return o}function N(t,{fast:e=!1}={}){if(e)return t();"requestIdleCallback"in window?requestIdleCallback(t,{timeout:50}):setTimeout(t,1)}function A(t,e=null){if(!(t instanceof Element))return window.getComputedStyle(t,e);let o=R.get(t);if(o||(o=new Map,R.set(t,o)),!o.has(e)){let n=window.getComputedStyle(t,e);o.set(e,n)}return o.get(e)}function Y(t){let e=t.replace(/^['"]|['"]$/g,"");if(e.startsWith("\\"))try{return String.fromCharCode(parseInt(e.replace("\\",""),16))}catch{return e}return e}function X(t){let e=t.match(/url\(["']?([^"')]+)["']?\)/);return e?e[1]:null}function M(t){return[/font\s*awesome/i,/material\s*icons/i,/ionicons/i,/glyphicons/i,/feather/i,/bootstrap\s*icons/i,/remix\s*icons/i,/heroicons/i].some(o=>o.test(t))}function C(t,e=3e3){return S.has(t)?Promise.resolve(S.get(t)):new Promise((o,n)=>{let a=setTimeout(()=>{n(new Error("Image load timed out"))},e),r=new Image;r.crossOrigin="anonymous",r.onload=async()=>{clearTimeout(a);try{await r.decode();let s=document.createElement("canvas");s.width=r.width,s.height=r.height,s.getContext("2d").drawImage(r,0,0);try{let i=s.toDataURL("image/png");S.set(t,i),o(i)}catch{n(new Error("CORS restrictions prevented image capture"))}}catch(s){n(s)}},r.onerror=s=>{clearTimeout(a),n(new Error("Failed to load image: "+(s.message||"Unknown error")))},r.src=t})}function B(t){let e={};for(let o of t)e[o]=t.getPropertyValue(o);return e}function F(){return/^((?!chrome|android).)*safari/i.test(navigator.userAgent)}function O(t,e,o,n,a){if(t.tagName==="STYLE")return;n.has(t)||n.set(t,A(t));let r=n.get(t),s=B(r),c=t.tagName?.toLowerCase()||"div",i=U(s,c,a);o.set(e,i)}function D(t,e,o,n,a){if(t.nodeType===Node.ELEMENT_NODE&&t.getAttribute("data-capture")==="exclude"){let c=document.createElement("div"),i=t.getBoundingClientRect();return c.style.cssText=`display: inline-block; width: ${i.width}px; height: ${i.height}px; visibility: hidden;`,c}if(t.tagName==="IFRAME"){let c=document.createElement("div");return c.textContent="",c.style.cssText=`width: ${t.offsetWidth}px; height: ${t.offsetHeight}px; background: repeating-linear-gradient(45deg, #ddd, #ddd 5px, #f9f9f9 5px, #f9f9f9 10px);display: flex;align-items: center;justify-content: center;font-size: 12px;color: #555; border: 1px solid #aaa;`,c}if(t.nodeType===Node.ELEMENT_NODE&&t.getAttribute("data-capture")==="placeholder"){let c=t.cloneNode(!1);n.set(c,t),O(t,c,e,o,a);let i=document.createElement("div");return i.textContent=t.getAttribute("data-placeholder-text")||"",i.style.cssText="color: #666;font-size: 12px;text-align: center;line-height: 1.4;padding: 0.5em;box-sizing: border-box;",c.appendChild(i),c}if(t.tagName==="CANVAS"){let c=t.toDataURL(),i=document.createElement("img");return i.src=c,i.width=t.width,i.height=t.height,i.style.display="inline-block",i.style.width=`${t.width}px`,i.style.height=`${t.height}px`,i}if(t.nodeType===Node.TEXT_NODE){if(!t.textContent.trim())return null;if(t.parentElement?.shadowRoot){let i=t.parentElement.tagName.toLowerCase();if(customElements.get(i))return null}return t.cloneNode(!0)}if(t.nodeType!==Node.ELEMENT_NODE)return t.cloneNode(!0);let r=t.cloneNode(!1);n.set(r,t),O(t,r,e,o,a);let s=document.createDocumentFragment();if(t.childNodes.forEach(c=>{let i=D(c,e,o,n,a);i&&s.appendChild(i)}),r.appendChild(s),t.shadowRoot){let c=Array.from(t.shadowRoot.children).filter(d=>d.tagName!=="STYLE").map(d=>D(d,e,o,n)).filter(Boolean),i=document.createDocumentFragment();c.forEach(d=>i.appendChild(d)),r.appendChild(i)}return r}async function G(t,e,o,n=32,a="#000"){e=e.replace(/^['"]+|['"]+$/g,""),await document.fonts.ready,await document.fonts.load(`${n}px "${e}"`);let r=document.createElement("canvas");r.width=n,r.height=n;let s=r.getContext("2d");return s.font=o?`${o} ${n}px "${e}"`:` ${n}px "${e}"`,s.textAlign="center",s.textBaseline="middle",s.fillStyle=a,s.fillText(t,r.width/2,r.height/2),r.toDataURL()}async function j({ignoreIconFonts:t=!0,preCached:e=!1}){let o=Array.from(document.querySelectorAll('link[rel="stylesheet"]')).filter(a=>a.href),n="";for(let a of o)try{let s=await(await fetch(a.href)).text();if(t&&(M(a.href)||M(s))){console.log("\u23ED\uFE0F Ignorando icon font CSS:",a.href);continue}let c=/url\(([^)]+)\)/g,i=await Promise.all(Array.from(s.matchAll(c)).map(async l=>{let m=l[1].replace(/["']/g,"");if(m.startsWith("http")||(m=new URL(m,a.href).href),t&&M(m))return console.log("\u23ED\uFE0F Ignorando icon font URL:",m),null;if($.has(m))return{original:l[0],inlined:`url(${$.get(m)})`};try{let h=await(await fetch(m)).blob(),f=await new Promise(u=>{let w=new FileReader;w.onload=()=>u(w.result),w.readAsDataURL(h)});return $.set(m,f),{original:l[0],inlined:`url(${f})`}}catch{return console.warn("\u274C No pude fetch font",m),null}})),d=s;for(let l of i)l&&(d=d.replace(l.original,l.inlined));n+=d+`
|
|
3
|
+
`}catch{console.warn("\u274C No pude fetch CSS",a.href)}if(n&&e){let a=document.createElement("style");a.setAttribute("data-snapdom","embedFonts"),a.textContent=n,document.head.appendChild(a)}return n}async function W(t,e,o,n,a){if(!(t instanceof Element)||!(e instanceof Element))return;for(let c of["::before","::after"])try{let i=A(t,c);if(!i)continue;let d=i.getPropertyValue("content"),l=i.getPropertyValue("background-image"),m=d&&d!=="none"&&d!=='""'&&d!=="''",g=l&&l.startsWith("url(");if(m||g){let h=i.getPropertyValue("font-family"),f=parseInt(i.getPropertyValue("font-size"))||32,u=parseInt(i.getPropertyValue("font-weight"))||!1,w=i.getPropertyValue("color")||"#000",y=document.createElement("span");y.dataset.snapdomPseudo=c;let L=B(i),v=U(L,"span",a);o.set(y,v);let P=h&&/font.*awesome|material|bootstrap|glyphicons|ionicons|remixicon|simple-line-icons|octicons|feather|typicons|weathericons/i.test(h),x=Y(d);if(P&&x.length===1){let b=document.createElement("img");b.src=await G(x,h,u,f,w),b.style="display:block;width:100%;height:100%;object-fit:contain;",y.appendChild(b)}else if(x.startsWith("url(")){let b=x.match(/url\(["']?([^"')]+)["']?\)/);if(b?.[1])try{let I=document.createElement("img"),q=await C(b[1]);I.src=q,I.style="display:block;width:100%;height:100%;object-fit:contain;",y.appendChild(I)}catch(I){console.error(`[snapdom] Error in pseudo ${c} for`,t,I)}}else x&&x!=="none"&&(y.textContent=x);c==="::before"?e.insertBefore(y,e.firstChild):e.appendChild(y)}}catch(i){console.warn(`[snapdom] Failed to capture ${c} for`,t,i)}let r=Array.from(t.children),s=Array.from(e.children).filter(c=>!c.dataset.snapdomPseudo);for(let c=0;c<Math.min(r.length,s.length);c++)await W(r[c],s[c],o,n,a)}async function Q(t,e=!1){let o=new Map,n=new WeakMap,a=new WeakMap,r;try{r=D(t,o,n,a,e)}catch(i){throw console.warn("deepClone failed:",i),i}try{await W(t,r,o,n,e)}catch(i){console.warn("inlinePseudoElements failed:",i)}let s=J(o),c=Array.from(s.entries()).map(([i,d])=>`.${d}{${i}}`).join("");for(let[i,d]of o.entries()){if(i.tagName==="STYLE")continue;let l=s.get(d);l&&i.classList.add(l);let m=i.style?.backgroundImage;i.removeAttribute("style"),m&&m!=="none"&&(i.style.backgroundImage=m)}return{clone:r,classCSS:c,styleCache:n}}async function Z(t){let e=Array.from(t.querySelectorAll("img")),o=async n=>{let a=n.src;try{let r=await C(a);n.src=r,n.width||(n.width=n.naturalWidth||100),n.height||(n.height=n.naturalHeight||100)}catch{let r=document.createElement("div");r.style=`width: ${n.width||100}px; height: ${n.height||100}px; background: #ccc; display: inline-block; text-align: center; line-height: ${n.height||100}px; color: #666; font-size: 12px;`,r.innerText="img",n.replaceWith(r)}};for(let n=0;n<e.length;n+=4){let a=e.slice(n,n+4).map(o);await Promise.allSettled(a)}}async function tt(t,e,o){let n=[[t,e]];for(;n.length;){let[a,r]=n.shift(),s=o.get(a)||A(a);o.has(a)||o.set(a,s);let c=s.getPropertyValue("background-image");if(c&&c.includes("url(")){let l=c.match(/url\(["']?([^"')]+)["']?\)/);if(l?.[1])try{let m=l[1],g;E.has(m)?g=E.get(m):(g=await C(m),E.set(m,g)),r.style.backgroundImage=`url(${g})`}catch{r.style.backgroundImage="none"}}let i=Array.from(a.children),d=Array.from(r.children);for(let l=0;l<Math.min(i.length,d.length);l++)n.push([i[l],d[l]])}}async function et(t,e={}){if(!t)throw new Error("Element cannot be null or undefined");let{compress:o=!0,embedFonts:n=!1,fast:a=!0,scale:r=1}=e,s,c,i,d="",l="",m,g;if({clone:s,classCSS:c,styleCache:i}=await Q(t,o),await new Promise(f=>{N(async()=>{await Z(s),f()},{fast:a})}),await new Promise(f=>{N(async()=>{await tt(t,s,i),f()},{fast:a})}),n&&await new Promise(f=>{N(async()=>{d=await j({ignoreIconFonts:!0}),f()},{fast:a})}),o){let f=H(s).sort(),u=f.join(",");T.has(u)?l=T.get(u):await new Promise(w=>{N(()=>{l=K(f),T.set(u,l),w()},{fast:a})})}await new Promise(f=>{N(()=>{let u=t.getBoundingClientRect(),w=Math.ceil(u.width),y=Math.ceil(u.height);s.setAttribute("xmlns","http://www.w3.org/1999/xhtml"),s.style.transformOrigin="top left",r!==1&&F()&&(s.style.scale=`${r}`);let L="http://www.w3.org/2000/svg",v=document.createElementNS(L,"foreignObject");v.setAttribute("width","100%"),v.setAttribute("height","100%");let P=document.createElement("style");P.textContent=l+d+"svg{overflow:visible;}"+c,v.appendChild(P),v.appendChild(s);let b=new XMLSerializer().serializeToString(v);g=`<svg xmlns="${L}" width="${w}" height="${y}" viewBox="0 0 ${w} ${y}">`+b+"</svg>",m=`data:image/svg+xml;charset=utf-8,${encodeURIComponent(g)}`,f()},{fast:a})});let h=document.getElementById("snapdom-sandbox");return h&&h.style.position==="absolute"&&h.remove(),m}async function st(t,{dpr:e=1,scale:o=1}){let n=new Image;return n.src=t,await n.decode(),F?(n.width=n.width*o,n.height=n.height*o):(n.width=n.width/o,n.height=n.height/o),n}async function nt(t,{dpr:e=1,scale:o=1}){let n=new Image;n.src=t,await n.decode();let a=document.createElement("canvas"),r=n.width*o*e,s=n.height*o*e;a.width=Math.ceil(r),a.height=Math.ceil(s);let c=a.getContext("2d");return c.scale(o*e,o*e),c.drawImage(n,0,0),a}async function ot(t){let e=decodeURIComponent(t.split(",")[1]);return new Blob([e],{type:"image/svg+xml"})}async function at(t,{dpr:e=1,scale:o=1},n){let a=await nt(t,{dpr:e,scale:o});if(!n)return a;let r=document.createElement("canvas");r.width=a.width,r.height=a.height;let s=r.getContext("2d");return s.fillStyle=n,s.fillRect(0,0,r.width,r.height),s.drawImage(a,0,0),r}async function V(t,{dpr:e=1,scale:o=1,backgroundColor:n="#fff",quality:a},r="png"){let s=await at(t,{dpr:e,scale:o},n),c=new Image;return c.src=s.toDataURL(`image/${r}`,a),await c.decode(),c.style.width=`${s.width/e}px`,c.style.height=`${s.height/e}px`,c}async function it(t,{dpr:e=1,scale:o=1,backgroundColor:n="#fff",format:a="png",filename:r="capture"}={}){if(a==="svg"){let g=await ot(t),h=URL.createObjectURL(g),f=document.createElement("a");f.href=h,f.download=`${r}.svg`,f.click(),URL.revokeObjectURL(h);return}let s=["jpg","jpeg","webp"].includes(a)?"#fff":void 0,i=await at(t,{dpr:e,scale:o},n??s),d={jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",webp:"image/webp"}[a]||"image/png",l=i.toDataURL(d),m=document.createElement("a");m.href=l,m.download=`${r}.${a}`,m.click()}async function p(t,e={}){if(e={scale:1,...e},!t)throw new Error("Element cannot be null or undefined");return await p.capture(t,e)}p.capture=async(t,e={})=>{let o=await et(t,e),n=window.devicePixelRatio||1,a=e.scale||1;return{url:o,options:e,toRaw:()=>o,toImg:()=>st(o,{dpr:n,scale:a}),toCanvas:()=>nt(o,{dpr:n,scale:a}),toBlob:()=>ot(o),toPng:()=>V(o,{dpr:n,scale:a},"png"),toJpg:r=>V(o,{dpr:n,scale:a,...r},"jpeg"),toWebp:r=>V(o,{dpr:n,scale:a,...r},"webp"),download:({format:r="png",filename:s="capture",backgroundColor:c}={})=>it(o,{dpr:n,scale:a,backgroundColor:c,format:r,filename:s})}};p.toRaw=async(t,e)=>(await p.capture(t,e)).toRaw();p.toImg=async(t,e)=>(await p.capture(t,e)).toImg();p.toCanvas=async(t,e)=>(await p.capture(t,e)).toCanvas();p.toBlob=async(t,e)=>(await p.capture(t,e)).toBlob();p.toPng=async(t,e)=>(await p.capture(t,e)).toPng(e);p.toJpg=async(t,e)=>(await p.capture(t,e)).toJpg(e);p.toWebp=async(t,e)=>(await p.capture(t,e)).toWebp(e);p.download=async(t,e={})=>{let{format:o="png",filename:n="capture",backgroundColor:a,...r}=e;return await(await p.capture(t,r)).download({format:o,filename:n,backgroundColor:a})};async function ct(t=document,e={}){let{embedFonts:o=!0,reset:n=!1,preWarm:a=!0}=e;if(n){S.clear(),E.clear(),$.clear(),T.clear(),R.clear();return}await document.fonts.ready,_();let r=[],s=[];t?.querySelectorAll&&(r=Array.from(t.querySelectorAll("img[src]")),s=Array.from(t.querySelectorAll("*")));let c=[];for(let i of r){let d=i.src;S.has(d)||c.push(C(d).then(l=>S.set(d,l)).catch(()=>{}))}for(let i of s){let d=getComputedStyle(i).backgroundImage,l=X(d);l&&!E.has(l)&&c.push(C(l).then(m=>E.set(l,m)).catch(()=>{}))}o&&await j({ignoreIconFonts:!0,preCached:!0}),await Promise.all(c)}})();
|
package/dist/snapdom.min.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var S=new Map,E=new Map,$=new Map,
|
|
2
|
-
`;return o}function J(
|
|
3
|
-
`}catch{console.warn("\u274C No pude fetch CSS",a.href)}if(n&&
|
|
1
|
+
var S=new Map,E=new Map,$=new Map,k=new Map,T=new Map,R=new WeakMap;var rt=["div","span","p","a","img","ul","li","button","input","select","textarea","label","section","article","header","footer","nav","main","aside","h1","h2","h3","h4","h5","h6","svg","path","circle","rect","line","g","table","thead","tbody","tr","td","th"];function _(){for(let t of rt)z(t)}function z(t){if(k.has(t))return k.get(t);if(new Set(["script","style","meta","link","noscript","template"]).has(t)){let s={};return k.set(t,s),s}let o=document.getElementById("snapdom-sandbox");o||(o=document.createElement("div"),o.id="snapdom-sandbox",o.style.position="absolute",o.style.left="-9999px",o.style.top="-9999px",o.style.width="0",o.style.height="0",o.style.overflow="hidden",document.body.appendChild(o));let n=document.createElement(t);n.style.all="initial",o.appendChild(n);let a=getComputedStyle(n),r={};for(let s of a)r[s]=a.getPropertyValue(s);return o.removeChild(n),k.set(t,r),r}function U(t,e,o=!1){let n=[],a=z(e);for(let[r,s]of Object.entries(t))if(!o)s&&n.push(`${r}:${s}`);else{let c=a[r];s&&s!==c&&n.push(`${r}:${s}`)}return n.sort().join(";")}function H(t){let e=new Set;return t.nodeType!==Node.ELEMENT_NODE&&t.nodeType!==Node.DOCUMENT_FRAGMENT_NODE?[]:(t.tagName&&e.add(t.tagName.toLowerCase()),typeof t.querySelectorAll=="function"&&t.querySelectorAll("*").forEach(o=>e.add(o.tagName.toLowerCase())),Array.from(e))}function K(t){let e=new Map;for(let n of t){let a=k.get(n);if(!a)continue;let r=Object.entries(a).map(([s,c])=>`${s}:${c};`).sort().join("");e.has(r)||e.set(r,[]),e.get(r).push(n)}let o="";for(let[n,a]of e.entries())o+=`${a.join(",")} { ${n} }
|
|
2
|
+
`;return o}function J(t){let e=new Set(t.values()),o=new Map,n=1;for(let a of e)o.set(a,`c${n++}`);return o}function N(t,{fast:e=!1}={}){if(e)return t();"requestIdleCallback"in window?requestIdleCallback(t,{timeout:50}):setTimeout(t,1)}function A(t,e=null){if(!(t instanceof Element))return window.getComputedStyle(t,e);let o=R.get(t);if(o||(o=new Map,R.set(t,o)),!o.has(e)){let n=window.getComputedStyle(t,e);o.set(e,n)}return o.get(e)}function Y(t){let e=t.replace(/^['"]|['"]$/g,"");if(e.startsWith("\\"))try{return String.fromCharCode(parseInt(e.replace("\\",""),16))}catch{return e}return e}function X(t){let e=t.match(/url\(["']?([^"')]+)["']?\)/);return e?e[1]:null}function M(t){return[/font\s*awesome/i,/material\s*icons/i,/ionicons/i,/glyphicons/i,/feather/i,/bootstrap\s*icons/i,/remix\s*icons/i,/heroicons/i].some(o=>o.test(t))}function C(t,e=3e3){return S.has(t)?Promise.resolve(S.get(t)):new Promise((o,n)=>{let a=setTimeout(()=>{n(new Error("Image load timed out"))},e),r=new Image;r.crossOrigin="anonymous",r.onload=async()=>{clearTimeout(a);try{await r.decode();let s=document.createElement("canvas");s.width=r.width,s.height=r.height,s.getContext("2d").drawImage(r,0,0);try{let i=s.toDataURL("image/png");S.set(t,i),o(i)}catch{n(new Error("CORS restrictions prevented image capture"))}}catch(s){n(s)}},r.onerror=s=>{clearTimeout(a),n(new Error("Failed to load image: "+(s.message||"Unknown error")))},r.src=t})}function B(t){let e={};for(let o of t)e[o]=t.getPropertyValue(o);return e}function F(){return/^((?!chrome|android).)*safari/i.test(navigator.userAgent)}function O(t,e,o,n,a){if(t.tagName==="STYLE")return;n.has(t)||n.set(t,A(t));let r=n.get(t),s=B(r),c=t.tagName?.toLowerCase()||"div",i=U(s,c,a);o.set(e,i)}function D(t,e,o,n,a){if(t.nodeType===Node.ELEMENT_NODE&&t.getAttribute("data-capture")==="exclude"){let c=document.createElement("div"),i=t.getBoundingClientRect();return c.style.cssText=`display: inline-block; width: ${i.width}px; height: ${i.height}px; visibility: hidden;`,c}if(t.tagName==="IFRAME"){let c=document.createElement("div");return c.textContent="",c.style.cssText=`width: ${t.offsetWidth}px; height: ${t.offsetHeight}px; background: repeating-linear-gradient(45deg, #ddd, #ddd 5px, #f9f9f9 5px, #f9f9f9 10px);display: flex;align-items: center;justify-content: center;font-size: 12px;color: #555; border: 1px solid #aaa;`,c}if(t.nodeType===Node.ELEMENT_NODE&&t.getAttribute("data-capture")==="placeholder"){let c=t.cloneNode(!1);n.set(c,t),O(t,c,e,o,a);let i=document.createElement("div");return i.textContent=t.getAttribute("data-placeholder-text")||"",i.style.cssText="color: #666;font-size: 12px;text-align: center;line-height: 1.4;padding: 0.5em;box-sizing: border-box;",c.appendChild(i),c}if(t.tagName==="CANVAS"){let c=t.toDataURL(),i=document.createElement("img");return i.src=c,i.width=t.width,i.height=t.height,i.style.display="inline-block",i.style.width=`${t.width}px`,i.style.height=`${t.height}px`,i}if(t.nodeType===Node.TEXT_NODE){if(!t.textContent.trim())return null;if(t.parentElement?.shadowRoot){let i=t.parentElement.tagName.toLowerCase();if(customElements.get(i))return null}return t.cloneNode(!0)}if(t.nodeType!==Node.ELEMENT_NODE)return t.cloneNode(!0);let r=t.cloneNode(!1);n.set(r,t),O(t,r,e,o,a);let s=document.createDocumentFragment();if(t.childNodes.forEach(c=>{let i=D(c,e,o,n,a);i&&s.appendChild(i)}),r.appendChild(s),t.shadowRoot){let c=Array.from(t.shadowRoot.children).filter(d=>d.tagName!=="STYLE").map(d=>D(d,e,o,n)).filter(Boolean),i=document.createDocumentFragment();c.forEach(d=>i.appendChild(d)),r.appendChild(i)}return r}async function G(t,e,o,n=32,a="#000"){e=e.replace(/^['"]+|['"]+$/g,""),await document.fonts.ready,await document.fonts.load(`${n}px "${e}"`);let r=document.createElement("canvas");r.width=n,r.height=n;let s=r.getContext("2d");return s.font=o?`${o} ${n}px "${e}"`:` ${n}px "${e}"`,s.textAlign="center",s.textBaseline="middle",s.fillStyle=a,s.fillText(t,r.width/2,r.height/2),r.toDataURL()}async function j({ignoreIconFonts:t=!0,preCached:e=!1}){let o=Array.from(document.querySelectorAll('link[rel="stylesheet"]')).filter(a=>a.href),n="";for(let a of o)try{let s=await(await fetch(a.href)).text();if(t&&(M(a.href)||M(s))){console.log("\u23ED\uFE0F Ignorando icon font CSS:",a.href);continue}let c=/url\(([^)]+)\)/g,i=await Promise.all(Array.from(s.matchAll(c)).map(async l=>{let m=l[1].replace(/["']/g,"");if(m.startsWith("http")||(m=new URL(m,a.href).href),t&&M(m))return console.log("\u23ED\uFE0F Ignorando icon font URL:",m),null;if($.has(m))return{original:l[0],inlined:`url(${$.get(m)})`};try{let h=await(await fetch(m)).blob(),f=await new Promise(u=>{let w=new FileReader;w.onload=()=>u(w.result),w.readAsDataURL(h)});return $.set(m,f),{original:l[0],inlined:`url(${f})`}}catch{return console.warn("\u274C No pude fetch font",m),null}})),d=s;for(let l of i)l&&(d=d.replace(l.original,l.inlined));n+=d+`
|
|
3
|
+
`}catch{console.warn("\u274C No pude fetch CSS",a.href)}if(n&&e){let a=document.createElement("style");a.setAttribute("data-snapdom","embedFonts"),a.textContent=n,document.head.appendChild(a)}return n}async function W(t,e,o,n,a){if(!(t instanceof Element)||!(e instanceof Element))return;for(let c of["::before","::after"])try{let i=A(t,c);if(!i)continue;let d=i.getPropertyValue("content"),l=i.getPropertyValue("background-image"),m=d&&d!=="none"&&d!=='""'&&d!=="''",g=l&&l.startsWith("url(");if(m||g){let h=i.getPropertyValue("font-family"),f=parseInt(i.getPropertyValue("font-size"))||32,u=parseInt(i.getPropertyValue("font-weight"))||!1,w=i.getPropertyValue("color")||"#000",y=document.createElement("span");y.dataset.snapdomPseudo=c;let L=B(i),v=U(L,"span",a);o.set(y,v);let P=h&&/font.*awesome|material|bootstrap|glyphicons|ionicons|remixicon|simple-line-icons|octicons|feather|typicons|weathericons/i.test(h),x=Y(d);if(P&&x.length===1){let b=document.createElement("img");b.src=await G(x,h,u,f,w),b.style="display:block;width:100%;height:100%;object-fit:contain;",y.appendChild(b)}else if(x.startsWith("url(")){let b=x.match(/url\(["']?([^"')]+)["']?\)/);if(b?.[1])try{let I=document.createElement("img"),q=await C(b[1]);I.src=q,I.style="display:block;width:100%;height:100%;object-fit:contain;",y.appendChild(I)}catch(I){console.error(`[snapdom] Error in pseudo ${c} for`,t,I)}}else x&&x!=="none"&&(y.textContent=x);c==="::before"?e.insertBefore(y,e.firstChild):e.appendChild(y)}}catch(i){console.warn(`[snapdom] Failed to capture ${c} for`,t,i)}let r=Array.from(t.children),s=Array.from(e.children).filter(c=>!c.dataset.snapdomPseudo);for(let c=0;c<Math.min(r.length,s.length);c++)await W(r[c],s[c],o,n,a)}async function Q(t,e=!1){let o=new Map,n=new WeakMap,a=new WeakMap,r;try{r=D(t,o,n,a,e)}catch(i){throw console.warn("deepClone failed:",i),i}try{await W(t,r,o,n,e)}catch(i){console.warn("inlinePseudoElements failed:",i)}let s=J(o),c=Array.from(s.entries()).map(([i,d])=>`.${d}{${i}}`).join("");for(let[i,d]of o.entries()){if(i.tagName==="STYLE")continue;let l=s.get(d);l&&i.classList.add(l);let m=i.style?.backgroundImage;i.removeAttribute("style"),m&&m!=="none"&&(i.style.backgroundImage=m)}return{clone:r,classCSS:c,styleCache:n}}async function Z(t){let e=Array.from(t.querySelectorAll("img")),o=async n=>{let a=n.src;try{let r=await C(a);n.src=r,n.width||(n.width=n.naturalWidth||100),n.height||(n.height=n.naturalHeight||100)}catch{let r=document.createElement("div");r.style=`width: ${n.width||100}px; height: ${n.height||100}px; background: #ccc; display: inline-block; text-align: center; line-height: ${n.height||100}px; color: #666; font-size: 12px;`,r.innerText="img",n.replaceWith(r)}};for(let n=0;n<e.length;n+=4){let a=e.slice(n,n+4).map(o);await Promise.allSettled(a)}}async function tt(t,e,o){let n=[[t,e]];for(;n.length;){let[a,r]=n.shift(),s=o.get(a)||A(a);o.has(a)||o.set(a,s);let c=s.getPropertyValue("background-image");if(c&&c.includes("url(")){let l=c.match(/url\(["']?([^"')]+)["']?\)/);if(l?.[1])try{let m=l[1],g;E.has(m)?g=E.get(m):(g=await C(m),E.set(m,g)),r.style.backgroundImage=`url(${g})`}catch{r.style.backgroundImage="none"}}let i=Array.from(a.children),d=Array.from(r.children);for(let l=0;l<Math.min(i.length,d.length);l++)n.push([i[l],d[l]])}}async function et(t,e={}){if(!t)throw new Error("Element cannot be null or undefined");let{compress:o=!0,embedFonts:n=!1,fast:a=!0,scale:r=1}=e,s,c,i,d="",l="",m,g;if({clone:s,classCSS:c,styleCache:i}=await Q(t,o),await new Promise(f=>{N(async()=>{await Z(s),f()},{fast:a})}),await new Promise(f=>{N(async()=>{await tt(t,s,i),f()},{fast:a})}),n&&await new Promise(f=>{N(async()=>{d=await j({ignoreIconFonts:!0}),f()},{fast:a})}),o){let f=H(s).sort(),u=f.join(",");T.has(u)?l=T.get(u):await new Promise(w=>{N(()=>{l=K(f),T.set(u,l),w()},{fast:a})})}await new Promise(f=>{N(()=>{let u=t.getBoundingClientRect(),w=Math.ceil(u.width),y=Math.ceil(u.height);s.setAttribute("xmlns","http://www.w3.org/1999/xhtml"),s.style.transformOrigin="top left",r!==1&&F()&&(s.style.scale=`${r}`);let L="http://www.w3.org/2000/svg",v=document.createElementNS(L,"foreignObject");v.setAttribute("width","100%"),v.setAttribute("height","100%");let P=document.createElement("style");P.textContent=l+d+"svg{overflow:visible;}"+c,v.appendChild(P),v.appendChild(s);let b=new XMLSerializer().serializeToString(v);g=`<svg xmlns="${L}" width="${w}" height="${y}" viewBox="0 0 ${w} ${y}">`+b+"</svg>",m=`data:image/svg+xml;charset=utf-8,${encodeURIComponent(g)}`,f()},{fast:a})});let h=document.getElementById("snapdom-sandbox");return h&&h.style.position==="absolute"&&h.remove(),m}async function st(t,{dpr:e=1,scale:o=1}){let n=new Image;return n.src=t,await n.decode(),F?(n.width=n.width*o,n.height=n.height*o):(n.width=n.width/o,n.height=n.height/o),n}async function nt(t,{dpr:e=1,scale:o=1}){let n=new Image;n.src=t,await n.decode();let a=document.createElement("canvas"),r=n.width*o*e,s=n.height*o*e;a.width=Math.ceil(r),a.height=Math.ceil(s);let c=a.getContext("2d");return c.scale(o*e,o*e),c.drawImage(n,0,0),a}async function ot(t){let e=decodeURIComponent(t.split(",")[1]);return new Blob([e],{type:"image/svg+xml"})}async function at(t,{dpr:e=1,scale:o=1},n){let a=await nt(t,{dpr:e,scale:o});if(!n)return a;let r=document.createElement("canvas");r.width=a.width,r.height=a.height;let s=r.getContext("2d");return s.fillStyle=n,s.fillRect(0,0,r.width,r.height),s.drawImage(a,0,0),r}async function V(t,{dpr:e=1,scale:o=1,backgroundColor:n="#fff",quality:a},r="png"){let s=await at(t,{dpr:e,scale:o},n),c=new Image;return c.src=s.toDataURL(`image/${r}`,a),await c.decode(),c.style.width=`${s.width/e}px`,c.style.height=`${s.height/e}px`,c}async function it(t,{dpr:e=1,scale:o=1,backgroundColor:n="#fff",format:a="png",filename:r="capture"}={}){if(a==="svg"){let g=await ot(t),h=URL.createObjectURL(g),f=document.createElement("a");f.href=h,f.download=`${r}.svg`,f.click(),URL.revokeObjectURL(h);return}let s=["jpg","jpeg","webp"].includes(a)?"#fff":void 0,i=await at(t,{dpr:e,scale:o},n??s),d={jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",webp:"image/webp"}[a]||"image/png",l=i.toDataURL(d),m=document.createElement("a");m.href=l,m.download=`${r}.${a}`,m.click()}async function p(t,e={}){if(e={scale:1,...e},!t)throw new Error("Element cannot be null or undefined");return await p.capture(t,e)}p.capture=async(t,e={})=>{let o=await et(t,e),n=window.devicePixelRatio||1,a=e.scale||1;return{url:o,options:e,toRaw:()=>o,toImg:()=>st(o,{dpr:n,scale:a}),toCanvas:()=>nt(o,{dpr:n,scale:a}),toBlob:()=>ot(o),toPng:()=>V(o,{dpr:n,scale:a},"png"),toJpg:r=>V(o,{dpr:n,scale:a,...r},"jpeg"),toWebp:r=>V(o,{dpr:n,scale:a,...r},"webp"),download:({format:r="png",filename:s="capture",backgroundColor:c}={})=>it(o,{dpr:n,scale:a,backgroundColor:c,format:r,filename:s})}};p.toRaw=async(t,e)=>(await p.capture(t,e)).toRaw();p.toImg=async(t,e)=>(await p.capture(t,e)).toImg();p.toCanvas=async(t,e)=>(await p.capture(t,e)).toCanvas();p.toBlob=async(t,e)=>(await p.capture(t,e)).toBlob();p.toPng=async(t,e)=>(await p.capture(t,e)).toPng(e);p.toJpg=async(t,e)=>(await p.capture(t,e)).toJpg(e);p.toWebp=async(t,e)=>(await p.capture(t,e)).toWebp(e);p.download=async(t,e={})=>{let{format:o="png",filename:n="capture",backgroundColor:a,...r}=e;return await(await p.capture(t,r)).download({format:o,filename:n,backgroundColor:a})};async function ct(t=document,e={}){let{embedFonts:o=!0,reset:n=!1,preWarm:a=!0}=e;if(n){S.clear(),E.clear(),$.clear(),T.clear(),R.clear();return}await document.fonts.ready,_();let r=[],s=[];t?.querySelectorAll&&(r=Array.from(t.querySelectorAll("img[src]")),s=Array.from(t.querySelectorAll("*")));let c=[];for(let i of r){let d=i.src;S.has(d)||c.push(C(d).then(l=>S.set(d,l)).catch(()=>{}))}for(let i of s){let d=getComputedStyle(i).backgroundImage,l=X(d);l&&!E.has(l)&&c.push(C(l).then(m=>E.set(l,m)).catch(()=>{}))}o&&await j({ignoreIconFonts:!0,preCached:!0}),await Promise.all(c)}export{ct as preCache,p as snapdom};
|
package/dist/snapdom.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zumer/snapdom",
|
|
3
|
-
"version": "1.0.0
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "snapDOM captures DOM elements as images with exceptional speed and minimal overhead. Designed to avoid bottlenecks and long tasks.",
|
|
5
5
|
"main": "dist/snapdom.js",
|
|
6
6
|
"module": "dist/snapdom.mjs",
|