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
|
});
|
package/imagePreviewBootstrap.js
CHANGED
@@ -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
|
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
|
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.
|
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
|
-
"
|
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).
|