next-blurhash-previews 0.0.4-beta1 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -32,11 +32,6 @@ class ImageWithPreview extends HTMLElement {
32
32
  if (this.#imgEl && this.#canvasEl) {
33
33
  this.mo?.disconnect();
34
34
 
35
- console.log("checkready", this.#imgEl.complete);
36
-
37
- setInterval(() => {
38
- console.log("checkready interval", this.#imgEl.complete);
39
- }, 100);
40
35
  if (this.#imgEl.complete) {
41
36
  this.#imgLoad();
42
37
  } else {
@@ -61,7 +56,6 @@ class ImageWithPreview extends HTMLElement {
61
56
  }
62
57
 
63
58
  #imgLoad = () => {
64
- console.log("imgLoad - should work", this.#imgEl.complete);
65
59
  this.sd.innerHTML = `<slot name="image"></slot>`;
66
60
  };
67
61
 
@@ -114,7 +108,6 @@ function updateBlurHashPreview(
114
108
  ctx.putImageData(imageData, 0, 0);
115
109
 
116
110
  const end = +new Date();
117
- console.log("Done Encoding Sync", blurhash, end - start);
118
111
  } else if (canvasEl.transferControlToOffscreen) {
119
112
  const offscreen = canvasEl.transferControlToOffscreen();
120
113
  worker.postMessage({ canvas: offscreen, width, height, blurhash }, [
@@ -1,11 +1,8 @@
1
1
  import { decode } from "blurhash/dist/esm";
2
2
 
3
3
  addEventListener("message", evt => {
4
- console.log(evt);
5
-
6
4
  const { canvas, width, height, blurhash } = evt.data;
7
5
 
8
- console.log("Encoding", blurhash);
9
6
  const start = +new Date();
10
7
  const pixels = decode(blurhash, width, height);
11
8
  const ctx = canvas.getContext("2d");
@@ -13,5 +10,4 @@ addEventListener("message", evt => {
13
10
  imageData.data.set(pixels);
14
11
  ctx.putImageData(imageData, 0, 0);
15
12
  const end = +new Date();
16
- console.log("Done Encoding", blurhash, end - start);
17
13
  });
@@ -6,9 +6,9 @@ export const imagePreviewBootstrap = createElement(
6
6
  createElement("script", {
7
7
  id: "next-blurhash-worker-script",
8
8
  type: "javascript/worker",
9
- dangerouslySetInnerHTML: { __html: `(() => { "use strict";const I=["0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","#","$","%","*","+",",","-",".",":",";","=","?","@","[","]","^","_","{","|","}","~"],u=t=>{let n=0;for(let o=0;o<t.length;o++){const e=t[o],c=I.indexOf(e);n=n*83+c}return n},D=t=>{let n=t/255;return n<=.04045?n/12.92:Math.pow((n+.055)/1.055,2.4)},b=t=>{let n=Math.max(0,Math.min(1,t));return n<=.0031308?Math.round(n*12.92*255+.5):Math.round((1.055*Math.pow(n,.4166666666666667)-.055)*255+.5)},P=t=>t<0?-1:1,p=(t,n)=>P(t)*Math.pow(Math.abs(t),n);class q extends Error{constructor(n){super(n),this.name="ValidationError",this.message=n}}const v=t=>{if(!t||t.length<6)throw new q("The blurhash string must be at least 6 characters");const n=u(t[0]),o=Math.floor(n/9)+1,e=n%9+1;if(t.length!==4+2*e*o)throw new q(\`blurhash length mismatch: length is \${t.length} but it should be \${4+2*e*o}\`)},A=t=>{const n=t>>16,o=t>>8&255,e=t&255;return[D(n),D(o),D(e)]},T=(t,n)=>{const o=Math.floor(t/361),e=Math.floor(t/19)%19,c=t%19;return[p((o-9)/9,2)*n,p((e-9)/9,2)*n,p((c-9)/9,2)*n]},V=(t,n,o,e)=>{v(t),e=e|1;const c=u(t[0]),i=Math.floor(c/9)+1,l=c%9+1,h=(u(t[1])+1)/166,r=new Array(l*i);for(let s=0;s<r.length;s++)if(s===0){const a=u(t.substring(2,6));r[s]=A(a)}else{const a=u(t.substring(4+s*2,6+s*2));r[s]=T(a,h*e)}const d=n*4,g=new Uint8ClampedArray(d*o);for(let s=0;s<o;s++)for(let a=0;a<n;a++){let B=0,E=0,R=0;for(let M=0;M<i;M++)for(let f=0;f<l;f++){const w=Math.cos(Math.PI*a*f/n)*Math.cos(Math.PI*s*M/o);let x=r[f+M*l];B+=x[0]*w,E+=x[1]*w,R+=x[2]*w}let C=b(B),G=b(E),y=b(R);g[4*a+0+s*d]=C,g[4*a+1+s*d]=G,g[4*a+2+s*d]=y,g[4*a+3+s*d]=255}return g};var $=V;addEventListener("message",t=>{console.log(t);const{canvas:n,width:o,height:e,blurhash:c}=t.data;console.log("Encoding",c);const i=+new Date,l=$(c,o,e),m=n.getContext("2d"),h=m.createImageData(o,e);h.data.set(l),m.putImageData(h,0,0);const r=+new Date;console.log("Done Encoding",c,r-i)}); })();` },
9
+ dangerouslySetInnerHTML: { __html: `(() => { "use strict";const E=["0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","#","$","%","*","+",",","-",".",":",";","=","?","@","[","]","^","_","{","|","}","~"],g=t=>{let n=0;for(let e=0;e<t.length;e++){const o=t[e],r=E.indexOf(o);n=n*83+r}return n},x=t=>{let n=t/255;return n<=.04045?n/12.92:Math.pow((n+.055)/1.055,2.4)},b=t=>{let n=Math.max(0,Math.min(1,t));return n<=.0031308?Math.round(n*12.92*255+.5):Math.round((1.055*Math.pow(n,.4166666666666667)-.055)*255+.5)},I=t=>t<0?-1:1,p=(t,n)=>I(t)*Math.pow(Math.abs(t),n);class C extends Error{constructor(n){super(n),this.name="ValidationError",this.message=n}}const P=t=>{if(!t||t.length<6)throw new C("The blurhash string must be at least 6 characters");const n=g(t[0]),e=Math.floor(n/9)+1,o=n%9+1;if(t.length!==4+2*o*e)throw new C(\`blurhash length mismatch: length is \${t.length} but it should be \${4+2*o*e}\`)},A=t=>{const n=t>>16,e=t>>8&255,o=t&255;return[x(n),x(e),x(o)]},T=(t,n)=>{const e=Math.floor(t/361),o=Math.floor(t/19)%19,r=t%19;return[p((e-9)/9,2)*n,p((o-9)/9,2)*n,p((r-9)/9,2)*n]},V=(t,n,e,o)=>{P(t),o=o|1;const r=g(t[0]),i=Math.floor(r/9)+1,c=r%9+1,G=(g(t[1])+1)/166,u=new Array(c*i);for(let s=0;s<u.length;s++)if(s===0){const a=g(t.substring(2,6));u[s]=A(a)}else{const a=g(t.substring(4+s*2,6+s*2));u[s]=T(a,G*o)}const l=n*4,d=new Uint8ClampedArray(l*e);for(let s=0;s<e;s++)for(let a=0;a<n;a++){let B=0,R=0,q=0;for(let h=0;h<i;h++)for(let M=0;M<c;M++){const m=Math.cos(Math.PI*a*M/n)*Math.cos(Math.PI*s*h/e);let w=u[M+h*c];B+=w[0]*m,R+=w[1]*m,q+=w[2]*m}let v=b(B),y=b(R),D=b(q);d[4*a+0+s*l]=v,d[4*a+1+s*l]=y,d[4*a+2+s*l]=D,d[4*a+3+s*l]=255}return d};var $=V;addEventListener("message",t=>{const{canvas:n,width:e,height:o,blurhash:r}=t.data,i=$(r,e,o),c=n.getContext("2d"),f=c.createImageData(e,o);f.data.set(i),c.putImageData(f,0,0)}); })();` },
10
10
  }),
11
11
  createElement("script", {
12
- dangerouslySetInnerHTML: { __html: `(() => { "use strict";const T=["0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","#","$","%","*","+",",","-",".",":",";","=","?","@","[","]","^","_","{","|","}","~"],u=e=>{let t=0;for(let o=0;o<e.length;o++){const s=e[o],r=T.indexOf(s);t=t*83+r}return t},p=e=>{let t=e/255;return t<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4)},M=e=>{let t=Math.max(0,Math.min(1,e));return t<=.0031308?Math.round(t*12.92*255+.5):Math.round((1.055*Math.pow(t,.4166666666666667)-.055)*255+.5)},A=e=>e<0?-1:1,v=(e,t)=>A(e)*Math.pow(Math.abs(e),t);class L extends Error{constructor(t){super(t),this.name="ValidationError",this.message=t}}const D=e=>{if(!e||e.length<6)throw new L("The blurhash string must be at least 6 characters");const t=u(e[0]),o=Math.floor(t/9)+1,s=t%9+1;if(e.length!==4+2*s*o)throw new L(\`blurhash length mismatch: length is \${e.length} but it should be \${4+2*s*o}\`)},O=e=>{const t=e>>16,o=e>>8&255,s=e&255;return[p(t),p(o),p(s)]},P=(e,t)=>{const o=Math.floor(e/361),s=Math.floor(e/19)%19,r=e%19;return[v((o-9)/9,2)*t,v((s-9)/9,2)*t,v((r-9)/9,2)*t]},E=(e,t,o,s)=>{D(e),s=s|1;const r=u(e[0]),a=Math.floor(r/9)+1,c=r%9+1,g=(u(e[1])+1)/166,l=new Array(c*a);for(let n=0;n<l.length;n++)if(n===0){const i=u(e.substring(2,6));l[n]=O(i)}else{const i=u(e.substring(4+n*2,6+n*2));l[n]=P(i,g*s)}const h=t*4,d=new Uint8ClampedArray(h*o);for(let n=0;n<o;n++)for(let i=0;i<t;i++){let y=0,C=0,k=0;for(let m=0;m<a;m++)for(let f=0;f<c;f++){const w=Math.cos(Math.PI*i*f/t)*Math.cos(Math.PI*n*m/o);let b=l[f+m*c];y+=b[0]*w,C+=b[1]*w,k+=b[2]*w}let B=M(y),R=M(C),q=M(k);d[4*i+0+n*h]=B,d[4*i+1+n*h]=R,d[4*i+2+n*h]=q,d[4*i+3+n*h]=255}return d};var I=E;class S extends HTMLElement{sd;mo;static observedAttributes=["preview"];#s=!1;get#t(){return this.querySelector("img")}get#e(){return this.querySelector("canvas")}constructor(){super(),this.sd=this.attachShadow({mode:"open"}),this.sd.innerHTML='<slot name="preview"></slot>'}#o=()=>{if(this.#t&&this.#e)return this.mo?.disconnect(),console.log("checkready",this.#t.complete),setInterval(()=>{console.log("checkready interval",this.#t.complete)},100),this.#t.complete?this.#n():(this.#r(),this.#t.addEventListener("load",this.#n)),1};connectedCallback(){this.#s=!0,this.#o()||(this.mo=new MutationObserver(this.#o),this.mo.observe(this,{subtree:!0,childList:!0,attributes:!1}))}#n=()=>{console.log("imgLoad - should work",this.#t.complete),this.sd.innerHTML='<slot name="image"></slot>'};attributeChangedCallback(t){this.#e&&t==="preview"&&this.#r()}#r(){if(!this.#s||!this.getAttribute("preview"))return;const t=JSON.parse(this.getAttribute("preview"));H(this.hasAttribute("sync"),this.#e,t)}}customElements.get("blurhash-image")||customElements.define("blurhash-image",S);const G=new Blob([document.querySelector("#next-blurhash-worker-script").textContent],{type:"text/javascript"}),j=new Worker(window.URL.createObjectURL(G));function H(e,t,o){const{w:s,h:r,blurhash:a}=o;if(t.width=s,t.height=r,e){const c=+new Date,x=I(a,s,r),g=t.getContext("2d"),l=g.createImageData(s,r);l.data.set(x),g.putImageData(l,0,0);const h=+new Date;console.log("Done Encoding Sync",a,h-c)}else if(t.transferControlToOffscreen){const c=t.transferControlToOffscreen();j.postMessage({canvas:c,width:s,height:r,blurhash:a},[c])}} })();` },
12
+ dangerouslySetInnerHTML: { __html: `(() => { "use strict";const k=["0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","#","$","%","*","+",",","-",".",":",";","=","?","@","[","]","^","_","{","|","}","~"],u=e=>{let t=0;for(let n=0;n<e.length;n++){const s=e[n],r=k.indexOf(s);t=t*83+r}return t},p=e=>{let t=e/255;return t<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4)},v=e=>{let t=Math.max(0,Math.min(1,e));return t<=.0031308?Math.round(t*12.92*255+.5):Math.round((1.055*Math.pow(t,.4166666666666667)-.055)*255+.5)},A=e=>e<0?-1:1,x=(e,t)=>A(e)*Math.pow(Math.abs(e),t);class L extends Error{constructor(t){super(t),this.name="ValidationError",this.message=t}}const O=e=>{if(!e||e.length<6)throw new L("The blurhash string must be at least 6 characters");const t=u(e[0]),n=Math.floor(t/9)+1,s=t%9+1;if(e.length!==4+2*s*n)throw new L(\`blurhash length mismatch: length is \${e.length} but it should be \${4+2*s*n}\`)},P=e=>{const t=e>>16,n=e>>8&255,s=e&255;return[p(t),p(n),p(s)]},E=(e,t)=>{const n=Math.floor(e/361),s=Math.floor(e/19)%19,r=e%19;return[x((n-9)/9,2)*t,x((s-9)/9,2)*t,x((r-9)/9,2)*t]},G=(e,t,n,s)=>{O(e),s=s|1;const r=u(e[0]),c=Math.floor(r/9)+1,a=r%9+1,d=(u(e[1])+1)/166,g=new Array(a*c);for(let o=0;o<g.length;o++)if(o===0){const i=u(e.substring(2,6));g[o]=P(i)}else{const i=u(e.substring(4+o*2,6+o*2));g[o]=E(i,d*s)}const l=t*4,h=new Uint8ClampedArray(l*n);for(let o=0;o<n;o++)for(let i=0;i<t;i++){let C=0,y=0,B=0;for(let f=0;f<c;f++)for(let m=0;m<a;m++){const b=Math.cos(Math.PI*i*m/t)*Math.cos(Math.PI*o*f/n);let M=g[m+f*a];C+=M[0]*b,y+=M[1]*b,B+=M[2]*b}let R=v(C),q=v(y),T=v(B);h[4*i+0+o*l]=R,h[4*i+1+o*l]=q,h[4*i+2+o*l]=T,h[4*i+3+o*l]=255}return h};var I=G;class S extends HTMLElement{sd;mo;static observedAttributes=["preview"];#s=!1;get#t(){return this.querySelector("img")}get#e(){return this.querySelector("canvas")}constructor(){super(),this.sd=this.attachShadow({mode:"open"}),this.sd.innerHTML='<slot name="preview"></slot>'}#n=()=>{if(this.#t&&this.#e)return this.mo?.disconnect(),this.#t.complete?this.#o():(this.#r(),this.#t.addEventListener("load",this.#o)),1};connectedCallback(){this.#s=!0,this.#n()||(this.mo=new MutationObserver(this.#n),this.mo.observe(this,{subtree:!0,childList:!0,attributes:!1}))}#o=()=>{this.sd.innerHTML='<slot name="image"></slot>'};attributeChangedCallback(t){this.#e&&t==="preview"&&this.#r()}#r(){if(!this.#s||!this.getAttribute("preview"))return;const t=JSON.parse(this.getAttribute("preview"));H(this.hasAttribute("sync"),this.#e,t)}}customElements.get("blurhash-image")||customElements.define("blurhash-image",S);const j=new Blob([document.querySelector("#next-blurhash-worker-script").textContent],{type:"text/javascript"}),D=new Worker(window.URL.createObjectURL(j));function H(e,t,n){const{w:s,h:r,blurhash:c}=n;if(t.width=s,t.height=r,e){const a=I(c,s,r),w=t.getContext("2d"),d=w.createImageData(s,r);d.data.set(a),w.putImageData(d,0,0)}else if(t.transferControlToOffscreen){const a=t.transferControlToOffscreen();D.postMessage({canvas:a,width:s,height:r,blurhash:c},[a])}} })();` },
13
13
  })
14
14
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-blurhash-previews",
3
- "version": "0.0.4-beta1",
3
+ "version": "0.0.5",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "module": "index.js",
@@ -25,17 +25,19 @@
25
25
  },
26
26
  "homepage": "https://github.com/arackaf/next-static-image-previews#readme",
27
27
  "devDependencies": {
28
+ "react": "^18.2.0",
29
+ "react-dom": "^18.2.0",
28
30
  "@types/react": "^18.0.15",
29
31
  "@types/react-dom": "^18.0.6",
30
- "@vitejs/plugin-react": "^1.3.2",
32
+ "vite": "^2.9.13",
33
+ "@vitejs/plugin-react": "^1.3.2"
34
+ },
35
+ "dependencies": {
31
36
  "blurhash": "^1.1.5",
32
37
  "colors": "^1.4.0",
33
38
  "glob": "^8.0.3",
34
39
  "install": "^0.13.0",
35
40
  "node-fetch": "^3.2.6",
36
- "npm": "^8.15.1",
37
- "react": "^18.2.0",
38
- "react-dom": "^18.2.0",
39
41
  "remark": "^14.0.2",
40
42
  "remark-frontmatter": "^4.0.1",
41
43
  "retext": "^8.1.0",
@@ -43,7 +45,6 @@
43
45
  "to-vfile": "^7.2.3",
44
46
  "unist-util-visit": "^4.1.0",
45
47
  "unist-util-visit-parents": "^5.1.0",
46
- "vfile-reporter": "^7.0.4",
47
- "vite": "^2.9.13"
48
+ "vfile-reporter": "^7.0.4"
48
49
  }
49
50
  }
package/readme.md ADDED
@@ -0,0 +1,40 @@
1
+ # next-blurhash-previews
2
+
3
+ This library exposes a web component that shows a blurhash preview for an image, until the underlying image loads. It works in both markdown files and regular React pages, it supports synchronous Blurhash encoding, and also OffscreanCanvas to encode on a background thread. Lastly, it's fully SSR friendly, since it exposes a component to place a synchronous, inline script on your page to register the web component (the total script is only 5K minified, _before_ gzip).
4
+
5
+ This differs from Next's Image component, which can receive a `placeholder="blur"` attribute in two ways: it will work in Markdown (without requiring MDX setup), and it uses Blurhash for the previews, which tend to look a bit nicer than the base64 previews Next generates. But do test and verify on that latter piece; if you don't need Markdown support, and you're happy with the Next previews, (and you're able to statically import your images, which is required for the blur placeholder) then just use `placeholder="blur"` on the Next image component and be done.
6
+
7
+ ## Installation
8
+
9
+ ```
10
+ npm i next-blurhash-previews
11
+ ```
12
+
13
+ ## Registering the web component
14
+
15
+ To use a web component, you have to register it. Just add this import
16
+
17
+ ```js
18
+ import { imagePreviewBootstrap } from "next-blurhash-previews";
19
+ ```
20
+
21
+ and then render it anywhere before your content, for example in your \_document.js file.
22
+
23
+ ```jsx
24
+ export default class MyDocument extends Document {
25
+ render() {
26
+ return (
27
+ <Html lang="en" className="scheme3">
28
+ <Head />
29
+ <body className="line-numbers">
30
+ {imagePreviewBootstrap}
31
+ <Main />
32
+ <NextScript />
33
+ </body>
34
+ </Html>
35
+ );
36
+ }
37
+ }
38
+ ```
39
+
40
+ This will add a blocking script to your page. This is absolutely bad for perf in the general case, but this script is just 5kb minified, total (before gzip).