├── .babelrc ├── .editorconfig ├── .eslintrc ├── .eslintrc.js ├── .gitignore ├── .npmignore ├── .prettierrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── docs-assets └── thumbnail.jpg ├── package-lock.json ├── package.json ├── screenshot.png ├── src ├── blur.jsx └── lib │ └── StackBlur.js └── website ├── .editorconfig ├── .env ├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── public ├── example-kyoto.jpg ├── index.html ├── manifest.json ├── react-blur.jpg └── robots.txt └── src ├── app.jsx ├── index.js └── serviceWorker.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "react", 4 | "es2015", 5 | "stage-1" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | [*.md] 11 | max_line_length = off 12 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": [ 4 | "airbnb", 5 | "prettier", 6 | "prettier/react" 7 | ], 8 | "plugins": [ 9 | "prettier" 10 | ], 11 | "env": { 12 | "browser": true 13 | }, 14 | "rules":{ 15 | "prettier/prettier": [1, { "trailingComma": "es5", "singleQuote": true }] 16 | } 17 | } -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": [ 7 | "eslint:recommended", 8 | "plugin:react/recommended" 9 | ], 10 | "parserOptions": { 11 | "ecmaFeatures": { 12 | "jsx": true 13 | }, 14 | "ecmaVersion": 12, 15 | "sourceType": "module" 16 | }, 17 | "plugins": [ 18 | "react" 19 | ], 20 | "rules": { 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /dist 5 | .vercel 6 | /node_modules 7 | /.pnp 8 | .pnp.js 9 | 10 | # testing 11 | /coverage 12 | 13 | # production 14 | /build 15 | 16 | # misc 17 | .DS_Store 18 | .env.local 19 | .env.development.local 20 | .env.test.local 21 | .env.production.local 22 | 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | *.log 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "printWidth": 80, 4 | "editor.formatOnSave": true, 5 | "proseWrap": "always", 6 | "tabWidth": 2, 7 | "requireConfig": false, 8 | "useTabs": false, 9 | "trailingComma": "none", 10 | "bracketSpacing": true, 11 | "jsxBracketSameLine": false, 12 | "semi": true 13 | } 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 1.0.3 2 | - Remove `console.log`. 3 | 4 | # 1.0.2 5 | - Dependency updates. 6 | 7 | # 1.0.1 8 | - Fix resizing 9 | 10 | # 1.0.0 11 | - Add `requestAnimationFrame` to prevent extra work. 12 | - Update dependencies. 13 | 14 | # 0.6.0 15 | - Add conditional flag for resizing by @TheHaff 16 | 17 | # 0.5.2 18 | - Fix issue when the child canvas didn't fill the parent container. Thank @getmicah ! 19 | 20 | # 0.5.1 21 | - Prevents recursive calls caused by setting this.img.src to a falsey value. eg. an empty string. (thanks @joshgillies!). 22 | 23 | # 0.5.0 24 | - Fix gitignore for MAC 25 | - Fix undeclared vars 26 | - add onLoadFunction prop for hook into the blur loading. 27 | - make sure changing src keeps correct blurRadius 28 | - Do not reload image if the img src prop is relative (because it won't match this.img.src which is absolute) 29 | 30 | Thanks @iamJoeTaylor! 31 | 32 | # 0.3.1 33 | - Fix `react-addons-pure-render-mixin` dependency bug. (thanks @bogas04!). 34 | 35 | # 0.3.0 36 | - Added support for react 0.14.x (thanks @voronianski!). 37 | 38 | # 0.2.5 39 | - Update image if a new image src is passed in props [ab54d60d] 40 | 41 | # 0.2.4 42 | - Prevent the component to crash if there was a CORS error. 43 | 44 | # 0.2.3 45 | - Stop wasting renders on componentWillReceiveProps, using componentWillUpdate instead. 46 | 47 | # 0.2.0 48 | - Assets are now precompiled. 49 | - Changed `resizeSpeed` for `resizeInterval`. 50 | 51 | # v0.1.0 52 | - Added `resizeSpeed` prop. 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2019, Javier Bórquez 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [React Blur](https://javier.xyz/react-blur/) 2 | 3 | React component for creating blurred backgrounds using canvas. 4 | 5 | [![react-blur](./website/public/react-blur.jpg)](https://javier.xyz/react-blur/) 6 | 7 | ## Installation 8 | 9 | ```js 10 | npm install react-blur --save 11 | ``` 12 | 13 | ## Usage 14 | 15 | ```js 16 | import Blur from "react-blur"; 17 | ``` 18 | 19 | ### Example 20 | 21 | ```jsx 22 | 23 | The content. 24 | 25 | ``` 26 | 27 | For a complete example see the code in the [demo branch](https://github.com/javierbyte/react-blur/blob/gh-pages/src/js/app.jsx). 28 | 29 | #### Props 30 | 31 | - `img`: The image path. 32 | - `blurRadius`: Optional. The size of the blur radius. 33 | - `enableStyles`: Optional. Flag to include base styles inline, omit this to easily override. 34 | - `shouldResize`: Optional. If the canvas should re-render on resize? Defaults to true. 35 | - `resizeInterval`: Optional. How fast the canvas should re-render on resize? Defaults to 128ms. 36 | 37 | ### Contributing 38 | 39 | _Thanks to [Quasimodo](http://www.quasimondo.com/StackBlurForCanvas/StackBlurDemo.html) for the original stack blur algorithm._ 40 | -------------------------------------------------------------------------------- /docs-assets/thumbnail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javierbyte/react-blur/11395ceb644ab3b7072077ccd4f72053912f5c8f/docs-assets/thumbnail.jpg -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-blur", 3 | "version": "1.0.3", 4 | "description": "React component for creating blurred backgrounds.", 5 | "main": "dist/blur.js", 6 | "scripts": { 7 | "build": "mkdir -p dist/lib && babel src/blur.jsx -u -o dist/blur.js && babel src/lib/StackBlur.js -u -o dist/lib/StackBlur.js", 8 | "prepublish": "mkdir -p dist && babel src/blur.jsx -u -o dist/blur.js && babel src/lib/StackBlur.js -u -o dist/lib/StackBlur.js", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "keywords": [ 12 | "blur", 13 | "canvas", 14 | "component", 15 | "react", 16 | "react-component", 17 | "select", 18 | "text" 19 | ], 20 | "repository": { 21 | "type": "git", 22 | "url": "http://github.com/javierbyte/react-blur.git" 23 | }, 24 | "author": "hi@javier.xyz", 25 | "license": "BSD-3-Clause", 26 | "peerDependencies": { 27 | "react": "^16.8.2" 28 | }, 29 | "dependencies": { 30 | "prop-types": "^15.8.0" 31 | }, 32 | "devDependencies": { 33 | "babel-cli": "^6.9.0", 34 | "babel-eslint": "^10.1.0", 35 | "babel-preset-es2015": "^6.9.0", 36 | "babel-preset-react": "^6.5.0", 37 | "babel-preset-stage-1": "^6.5.0", 38 | "eslint": "^8.6.0", 39 | "eslint-config-airbnb": "^19.0.4", 40 | "eslint-config-prettier": "^8.3.0", 41 | "eslint-plugin-babel": "^5.3.1", 42 | "eslint-plugin-import": "^2.25.4", 43 | "eslint-plugin-jsx-a11y": "^6.5.1", 44 | "eslint-plugin-prettier": "^4.0.0", 45 | "eslint-plugin-react": "^7.28.0", 46 | "prettier": "^2.5.1" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javierbyte/react-blur/11395ceb644ab3b7072077ccd4f72053912f5c8f/screenshot.png -------------------------------------------------------------------------------- /src/blur.jsx: -------------------------------------------------------------------------------- 1 | import PropTypes from "prop-types"; 2 | import React, { useEffect, useRef } from "react"; 3 | import stackBlurImage from "./lib/StackBlur.js"; 4 | 5 | let animating = false; 6 | 7 | const ReactBlur = (props) => { 8 | const { 9 | blurRadius = 0, 10 | children = null, 11 | className = "", 12 | enableStyles = false, 13 | onLoadFunction = () => {}, 14 | shouldResize = false, 15 | resizeInterval = 64, 16 | img, 17 | ...other 18 | } = props; 19 | 20 | const containerRef = useRef(); 21 | const canvasRef = useRef(); 22 | const imgRef = useRef(); 23 | const widthRef = useRef(); 24 | const heightRef = useRef(); 25 | 26 | useEffect(() => { 27 | loadImage(); 28 | 29 | if (shouldResize) { 30 | window.addEventListener("resize", resize); 31 | } 32 | 33 | return () => { 34 | window.removeEventListener("resize", resize); 35 | }; 36 | }, []); 37 | 38 | useEffect(() => { 39 | if (!imgRef.current) { 40 | loadImage(props); 41 | } else if (!isCurrentImgSrc(img)) { 42 | imgRef.current.src = img; 43 | setDimensions(); 44 | } else { 45 | // if some other prop changed reblur 46 | if (animating) { 47 | return; 48 | } 49 | 50 | animating = true; 51 | 52 | window.requestAnimationFrame(() => { 53 | stackBlurImage( 54 | imgRef.current, 55 | canvasRef.current, 56 | blurRadius, 57 | widthRef.current, 58 | heightRef.current, 59 | () => { 60 | animating = false; 61 | } 62 | ); 63 | }); 64 | } 65 | }); 66 | 67 | const setDimensions = () => { 68 | heightRef.current = containerRef.current.offsetHeight; 69 | widthRef.current = containerRef.current.offsetWidth; 70 | canvasRef.current.height = heightRef.current; 71 | canvasRef.current.width = widthRef.current; 72 | }; 73 | 74 | const isCurrentImgSrc = (newSrc) => { 75 | // Handle relative paths 76 | if (imgRef.current) { 77 | const newImg = new Image(); 78 | newImg.src = newSrc; 79 | // if absolute SRC is the same 80 | return newImg.src === imgRef.current.src; 81 | } 82 | 83 | return false; 84 | }; 85 | 86 | const loadImage = () => { 87 | if (isCurrentImgSrc(imgRef.current)) { 88 | stackBlurImage( 89 | imgRef.current, 90 | canvasRef.current, 91 | blurRadius, 92 | widthRef.current, 93 | heightRef.current 94 | ); 95 | return; 96 | } 97 | 98 | imgRef.current = new Image(); 99 | imgRef.current.crossOrigin = "Anonymous"; 100 | 101 | imgRef.current.onload = (event) => { 102 | stackBlurImage( 103 | imgRef.current, 104 | canvasRef.current, 105 | blurRadius, 106 | widthRef.current, 107 | heightRef.current 108 | ); 109 | onLoadFunction(event); 110 | }; 111 | 112 | imgRef.current.onerror = (event) => { 113 | // Remove the onerror listener. 114 | // Preventing recursive calls caused by setting this.img.src to a falsey value 115 | imgRef.current.onerror = undefined; 116 | 117 | imgRef.current.src = ""; 118 | onLoadFunction(event); 119 | }; 120 | imgRef.current.src = img; 121 | 122 | setDimensions(); 123 | }; 124 | 125 | let last; 126 | const resize = () => { 127 | const now = new Date().getTime(); 128 | let deferTimer; 129 | const threshhold = resizeInterval; 130 | 131 | if (last && now < last + threshhold) { 132 | clearTimeout(deferTimer); 133 | deferTimer = setTimeout(() => { 134 | last = now; 135 | doResize(); 136 | }, threshhold); 137 | } else { 138 | last = now; 139 | doResize(); 140 | } 141 | }; 142 | 143 | const doResize = () => { 144 | setDimensions(); 145 | 146 | stackBlurImage( 147 | imgRef.current, 148 | canvasRef.current, 149 | blurRadius, 150 | widthRef.current, 151 | heightRef.current 152 | ); 153 | }; 154 | 155 | let classes = "react-blur"; 156 | if (className) { 157 | classes += ` ${className}`; 158 | } 159 | 160 | const containerStyle = enableStyles 161 | ? { 162 | position: "relative", 163 | } 164 | : {}; 165 | const canvasStyle = enableStyles 166 | ? { 167 | position: "absolute", 168 | top: 0, 169 | left: 0, 170 | } 171 | : {}; 172 | 173 | return ( 174 |
180 | 185 | {children} 186 |
187 | ); 188 | }; 189 | 190 | ReactBlur.propTypes = { 191 | blurRadius: PropTypes.number, 192 | children: PropTypes.node, 193 | className: PropTypes.string, 194 | enableStyles: PropTypes.bool, 195 | img: PropTypes.string.isRequired, 196 | onLoadFunction: PropTypes.func, 197 | shouldResize: PropTypes.bool, 198 | resizeInterval: PropTypes.number, 199 | }; 200 | 201 | export default ReactBlur; 202 | -------------------------------------------------------------------------------- /src/lib/StackBlur.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | var mul_table = [ 4 | 512, 512, 456, 512, 328, 456, 335, 512, 405, 328, 271, 456, 388, 335, 292, 512, 454, 405, 364, 328, 298, 271, 496, 5 | 456, 420, 388, 360, 335, 312, 292, 273, 512, 482, 454, 428, 405, 383, 364, 345, 328, 312, 298, 284, 271, 259, 496, 6 | 475, 456, 437, 420, 404, 388, 374, 360, 347, 335, 323, 312, 302, 292, 282, 273, 265, 512, 497, 482, 468, 454, 441, 7 | 428, 417, 405, 394, 383, 373, 364, 354, 345, 337, 328, 320, 312, 305, 298, 291, 284, 278, 271, 265, 259, 507, 496, 8 | 485, 475, 465, 456, 446, 437, 428, 420, 412, 404, 396, 388, 381, 374, 367, 360, 354, 347, 341, 335, 329, 323, 318, 9 | 312, 307, 302, 297, 292, 287, 282, 278, 273, 269, 265, 261, 512, 505, 497, 489, 482, 475, 468, 461, 454, 447, 441, 10 | 435, 428, 422, 417, 411, 405, 399, 394, 389, 383, 378, 373, 368, 364, 359, 354, 350, 345, 341, 337, 332, 328, 324, 11 | 320, 316, 312, 309, 305, 301, 298, 294, 291, 287, 284, 281, 278, 274, 271, 268, 265, 262, 259, 257, 507, 501, 496, 12 | 491, 485, 480, 475, 470, 465, 460, 456, 451, 446, 442, 437, 433, 428, 424, 420, 416, 412, 408, 404, 400, 396, 392, 13 | 388, 385, 381, 377, 374, 370, 367, 363, 360, 357, 354, 350, 347, 344, 341, 338, 335, 332, 329, 326, 323, 320, 318, 14 | 315, 312, 310, 307, 304, 302, 299, 297, 294, 292, 289, 287, 285, 282, 280, 278, 275, 273, 271, 269, 267, 265, 263, 15 | 261, 259, 16 | ]; 17 | 18 | var shg_table = [ 19 | 9, 11, 12, 13, 13, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 20 | 18, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21 | 20, 20, 20, 20, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22 | 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23 | 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 24 | 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 25 | 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 26 | 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 27 | 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 28 | ]; 29 | 30 | function stackBlurImage(img, canvas, radius, w, h, callback) { 31 | if (!w || !h) return; 32 | var nw = img.naturalWidth; 33 | var nh = img.naturalHeight; 34 | 35 | canvas.style.width = w + "px"; 36 | canvas.style.height = h + "px"; 37 | canvas.width = w; 38 | canvas.height = h; 39 | 40 | var context = canvas.getContext("2d"); 41 | context.clearRect(0, 0, w, h); 42 | 43 | var ratio = 1; 44 | if (nw / w > nh / h) { 45 | ratio = nh / h; 46 | } else { 47 | ratio = nw / w; 48 | } 49 | 50 | var drawW = nw / ratio; 51 | var drawH = nh / ratio; 52 | 53 | try { 54 | context.drawImage( 55 | img, 56 | Math.floor((drawW - w) / -2), 57 | Math.floor((drawH - h) / -2), 58 | Math.ceil(drawW), 59 | Math.ceil(drawH) 60 | ); 61 | } catch (e) { 62 | console.error("There was a problem drawing the image. " + e); 63 | } 64 | 65 | if (isNaN(radius) || radius < 1) { 66 | callback(null); 67 | return; 68 | } 69 | stackBlurCanvasRGB(canvas, 0, 0, w, h, radius, callback); 70 | } 71 | 72 | function stackBlurCanvasRGB(canvas, top_x, top_y, width, height, radius, callback) { 73 | if (isNaN(radius) || radius < 1) return; 74 | radius |= 0; 75 | 76 | var context = canvas.getContext("2d"); 77 | var imageData; 78 | 79 | try { 80 | imageData = context.getImageData(top_x, top_y, width, height); 81 | } catch (e) { 82 | throw new Error("unable to access image data: " + e); 83 | } 84 | 85 | var pixels = imageData.data; 86 | 87 | var x, 88 | y, 89 | i, 90 | p, 91 | yp, 92 | yi, 93 | yw, 94 | r_sum, 95 | g_sum, 96 | b_sum, 97 | r_out_sum, 98 | g_out_sum, 99 | b_out_sum, 100 | r_in_sum, 101 | g_in_sum, 102 | b_in_sum, 103 | pr, 104 | pg, 105 | pb, 106 | rbs; 107 | 108 | var div = radius + radius + 1; 109 | var w4 = width << 2; 110 | var widthMinus1 = width - 1; 111 | var heightMinus1 = height - 1; 112 | var radiusPlus1 = radius + 1; 113 | var sumFactor = (radiusPlus1 * (radiusPlus1 + 1)) / 2; 114 | 115 | var stackStart = new BlurStack(); 116 | var stack = stackStart; 117 | for (i = 1; i < div; i++) { 118 | stack = stack.next = new BlurStack(); 119 | if (i == radiusPlus1) var stackEnd = stack; 120 | } 121 | stack.next = stackStart; 122 | var stackIn = null; 123 | var stackOut = null; 124 | 125 | yw = yi = 0; 126 | 127 | var mul_sum = mul_table[radius]; 128 | var shg_sum = shg_table[radius]; 129 | 130 | for (y = 0; y < height; y++) { 131 | r_in_sum = g_in_sum = b_in_sum = r_sum = g_sum = b_sum = 0; 132 | 133 | r_out_sum = radiusPlus1 * (pr = pixels[yi]); 134 | g_out_sum = radiusPlus1 * (pg = pixels[yi + 1]); 135 | b_out_sum = radiusPlus1 * (pb = pixels[yi + 2]); 136 | 137 | r_sum += sumFactor * pr; 138 | g_sum += sumFactor * pg; 139 | b_sum += sumFactor * pb; 140 | 141 | stack = stackStart; 142 | 143 | for (i = 0; i < radiusPlus1; i++) { 144 | stack.r = pr; 145 | stack.g = pg; 146 | stack.b = pb; 147 | stack = stack.next; 148 | } 149 | 150 | for (i = 1; i < radiusPlus1; i++) { 151 | p = yi + ((widthMinus1 < i ? widthMinus1 : i) << 2); 152 | r_sum += (stack.r = pr = pixels[p]) * (rbs = radiusPlus1 - i); 153 | g_sum += (stack.g = pg = pixels[p + 1]) * rbs; 154 | b_sum += (stack.b = pb = pixels[p + 2]) * rbs; 155 | 156 | r_in_sum += pr; 157 | g_in_sum += pg; 158 | b_in_sum += pb; 159 | 160 | stack = stack.next; 161 | } 162 | 163 | stackIn = stackStart; 164 | stackOut = stackEnd; 165 | for (x = 0; x < width; x++) { 166 | pixels[yi] = (r_sum * mul_sum) >> shg_sum; 167 | pixels[yi + 1] = (g_sum * mul_sum) >> shg_sum; 168 | pixels[yi + 2] = (b_sum * mul_sum) >> shg_sum; 169 | 170 | r_sum -= r_out_sum; 171 | g_sum -= g_out_sum; 172 | b_sum -= b_out_sum; 173 | 174 | r_out_sum -= stackIn.r; 175 | g_out_sum -= stackIn.g; 176 | b_out_sum -= stackIn.b; 177 | 178 | p = (yw + ((p = x + radius + 1) < widthMinus1 ? p : widthMinus1)) << 2; 179 | 180 | r_in_sum += stackIn.r = pixels[p]; 181 | g_in_sum += stackIn.g = pixels[p + 1]; 182 | b_in_sum += stackIn.b = pixels[p + 2]; 183 | 184 | r_sum += r_in_sum; 185 | g_sum += g_in_sum; 186 | b_sum += b_in_sum; 187 | 188 | stackIn = stackIn.next; 189 | 190 | r_out_sum += pr = stackOut.r; 191 | g_out_sum += pg = stackOut.g; 192 | b_out_sum += pb = stackOut.b; 193 | 194 | r_in_sum -= pr; 195 | g_in_sum -= pg; 196 | b_in_sum -= pb; 197 | 198 | stackOut = stackOut.next; 199 | 200 | yi += 4; 201 | } 202 | yw += width; 203 | } 204 | 205 | for (x = 0; x < width; x++) { 206 | g_in_sum = b_in_sum = r_in_sum = g_sum = b_sum = r_sum = 0; 207 | 208 | yi = x << 2; 209 | r_out_sum = radiusPlus1 * (pr = pixels[yi]); 210 | g_out_sum = radiusPlus1 * (pg = pixels[yi + 1]); 211 | b_out_sum = radiusPlus1 * (pb = pixels[yi + 2]); 212 | 213 | r_sum += sumFactor * pr; 214 | g_sum += sumFactor * pg; 215 | b_sum += sumFactor * pb; 216 | 217 | stack = stackStart; 218 | 219 | for (i = 0; i < radiusPlus1; i++) { 220 | stack.r = pr; 221 | stack.g = pg; 222 | stack.b = pb; 223 | stack = stack.next; 224 | } 225 | 226 | yp = width; 227 | 228 | for (i = 1; i <= radius; i++) { 229 | yi = (yp + x) << 2; 230 | 231 | r_sum += (stack.r = pr = pixels[yi]) * (rbs = radiusPlus1 - i); 232 | g_sum += (stack.g = pg = pixels[yi + 1]) * rbs; 233 | b_sum += (stack.b = pb = pixels[yi + 2]) * rbs; 234 | 235 | r_in_sum += pr; 236 | g_in_sum += pg; 237 | b_in_sum += pb; 238 | 239 | stack = stack.next; 240 | 241 | if (i < heightMinus1) { 242 | yp += width; 243 | } 244 | } 245 | 246 | yi = x; 247 | stackIn = stackStart; 248 | stackOut = stackEnd; 249 | for (y = 0; y < height; y++) { 250 | p = yi << 2; 251 | pixels[p] = (r_sum * mul_sum) >> shg_sum; 252 | pixels[p + 1] = (g_sum * mul_sum) >> shg_sum; 253 | pixels[p + 2] = (b_sum * mul_sum) >> shg_sum; 254 | 255 | r_sum -= r_out_sum; 256 | g_sum -= g_out_sum; 257 | b_sum -= b_out_sum; 258 | 259 | r_out_sum -= stackIn.r; 260 | g_out_sum -= stackIn.g; 261 | b_out_sum -= stackIn.b; 262 | 263 | p = (x + ((p = y + radiusPlus1) < heightMinus1 ? p : heightMinus1) * width) << 2; 264 | 265 | r_sum += r_in_sum += stackIn.r = pixels[p]; 266 | g_sum += g_in_sum += stackIn.g = pixels[p + 1]; 267 | b_sum += b_in_sum += stackIn.b = pixels[p + 2]; 268 | 269 | stackIn = stackIn.next; 270 | 271 | r_out_sum += pr = stackOut.r; 272 | g_out_sum += pg = stackOut.g; 273 | b_out_sum += pb = stackOut.b; 274 | 275 | r_in_sum -= pr; 276 | g_in_sum -= pg; 277 | b_in_sum -= pb; 278 | 279 | stackOut = stackOut.next; 280 | 281 | yi += width; 282 | } 283 | } 284 | context.putImageData(imageData, top_x, top_y); 285 | if (callback) { 286 | callback(null); 287 | } 288 | } 289 | 290 | function BlurStack() { 291 | this.r = 0; 292 | this.g = 0; 293 | this.b = 0; 294 | this.a = 0; 295 | this.next = null; 296 | } 297 | 298 | module.exports = stackBlurImage; 299 | -------------------------------------------------------------------------------- /website/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | [*.md] 11 | max_line_length = off 12 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /website/.env: -------------------------------------------------------------------------------- 1 | SKIP_PREFLIGHT_CHECK=true -------------------------------------------------------------------------------- /website/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /dist 5 | .vercel 6 | /node_modules 7 | /.pnp 8 | .pnp.js 9 | 10 | # testing 11 | /coverage 12 | 13 | # production 14 | /build 15 | 16 | # misc 17 | .DS_Store 18 | .env.local 19 | .env.development.local 20 | .env.test.local 21 | .env.production.local 22 | 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | -------------------------------------------------------------------------------- /website/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "printWidth": 80, 4 | "editor.formatOnSave": true, 5 | "proseWrap": "always", 6 | "tabWidth": 2, 7 | "requireConfig": false, 8 | "useTabs": false, 9 | "trailingComma": "none", 10 | "bracketSpacing": true, 11 | "jsxBracketSameLine": false, 12 | "semi": true 13 | } 14 | -------------------------------------------------------------------------------- /website/LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2021, Javier Bórquez 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /website/README.md: -------------------------------------------------------------------------------- 1 | # react-blur-website 2 | -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-blur-website", 3 | "description": "React component for creating blurred backgrounds using canvas.", 4 | "version": "1.0.0", 5 | "private": true, 6 | "homepage": "https://javier.xyz/react-blur/", 7 | "dependencies": { 8 | "capsize": "^2.0.0", 9 | "jbx": "^1.7.5", 10 | "react": "^17.0.2", 11 | "react-blur": "^1.0.3", 12 | "react-dom": "^17.0.2", 13 | "react-scripts": "^5.0.0", 14 | "styled-components": "^5.3.3" 15 | }, 16 | "scripts": { 17 | "dev": "react-scripts start", 18 | "start": "react-scripts start", 19 | "build": "react-scripts build", 20 | "eject": "react-scripts eject", 21 | "predeploy": "npm run build", 22 | "deploy": "gh-pages -d build" 23 | }, 24 | "eslintConfig": { 25 | "extends": "react-app" 26 | }, 27 | "browserslist": { 28 | "production": [ 29 | ">0.5%", 30 | "not dead", 31 | "not IE 11", 32 | "not IE_Mob 11", 33 | "not op_mini all" 34 | ], 35 | "development": [ 36 | "last 1 chrome version", 37 | "last 1 firefox version", 38 | "last 1 safari version" 39 | ] 40 | }, 41 | "repository": { 42 | "type": "git", 43 | "url": "https://github.com/javierbyte/react-blur-website" 44 | }, 45 | "author": "Javier Bórquez ", 46 | "license": "BSD-3-Clause", 47 | "devDependencies": { 48 | "gh-pages": "^3.1.0" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /website/public/example-kyoto.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javierbyte/react-blur/11395ceb644ab3b7072077ccd4f72053912f5c8f/website/public/example-kyoto.jpg -------------------------------------------------------------------------------- /website/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | React Blur | React component for creating blurred backgrounds using canvas. 9 | 13 | 17 | 18 | 19 | 20 | 24 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 | 47 | 57 | 58 | 64 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /website/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React Blur", 3 | "name": "React component for creating blurred backgrounds using canvas.", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "theme_color": "#000000", 7 | "background_color": "#ffffff" 8 | } 9 | -------------------------------------------------------------------------------- /website/public/react-blur.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javierbyte/react-blur/11395ceb644ab3b7072077ccd4f72053912f5c8f/website/public/react-blur.jpg -------------------------------------------------------------------------------- /website/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /website/src/app.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | 3 | import { 4 | JBX, 5 | MainHeader, 6 | Text, 7 | Space, 8 | Container, 9 | Range, 10 | Box, 11 | A, 12 | CodeSnippet 13 | } from 'jbx'; 14 | 15 | import Blur from 'react-blur'; 16 | 17 | const exampleCode = ` 18 | // npm install react-blur 19 | import Blur from 'react-blur' 20 | [...] 21 | 22 | `.trim(); 23 | 24 | function App() { 25 | const [blurSrc, blurSet] = useState(16); 26 | 27 | const blur = Math.max(blurSrc, 0); 28 | 29 | return ( 30 | 31 | 32 | react-blur 33 | 34 | 35 | React component for creating blurred backgrounds using canvas. 36 | 37 | 38 | 39 | 48 | 49 | 50 | Amount of blur {blur}px 51 | 52 | 53 | blurSet(Number(e.target.value))} 58 | step={1} 59 | min={0} 60 | max={64} 61 | /> 62 | 63 | 64 | How to use: 65 | 66 | {exampleCode} 67 | 68 | 69 | For more information see the{' '} 70 | github repo. 71 | 72 | 73 | 74 | Made by javierbyte. 75 | 76 | 77 | ); 78 | } 79 | 80 | export default App; 81 | -------------------------------------------------------------------------------- /website/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './app.jsx'; 4 | 5 | ReactDOM.render(, document.getElementById('root')); 6 | -------------------------------------------------------------------------------- /website/src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.1/8 is considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl) 104 | .then(response => { 105 | // Ensure service worker exists, and that we really are getting a JS file. 106 | const contentType = response.headers.get('content-type'); 107 | if ( 108 | response.status === 404 || 109 | (contentType != null && contentType.indexOf('javascript') === -1) 110 | ) { 111 | // No service worker found. Probably a different app. Reload the page. 112 | navigator.serviceWorker.ready.then(registration => { 113 | registration.unregister().then(() => { 114 | window.location.reload(); 115 | }); 116 | }); 117 | } else { 118 | // Service worker found. Proceed as normal. 119 | registerValidSW(swUrl, config); 120 | } 121 | }) 122 | .catch(() => { 123 | console.log( 124 | 'No internet connection found. App is running in offline mode.' 125 | ); 126 | }); 127 | } 128 | 129 | export function unregister() { 130 | if ('serviceWorker' in navigator) { 131 | navigator.serviceWorker.ready.then(registration => { 132 | registration.unregister(); 133 | }); 134 | } 135 | } 136 | --------------------------------------------------------------------------------