Last active
November 13, 2016 18:54
-
-
Save idiotWu/2bd4a9c24f48ceaacaa65bd2a01ec1b9 to your computer and use it in GitHub Desktop.
DOM shadow blur algorithm: http://codepen.io/idiotWu/pen/YpwBdQ
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// demo config | |
const color = 'rgba(255, 0, 255, 1)'; | |
const radius = 50; | |
const offsetX = 50; | |
const offsetY = 50; | |
const width = 200; | |
const height = 100; | |
const dx = 100; | |
const dy = 100; | |
class Shadow { | |
static SQRT2PI = Math.sqrt(2 * Math.PI); | |
static getKernel1D(radius) { | |
const kernel = []; | |
const length = radius * 2 + 1; | |
const sigma = (radius / 2) || 1; | |
const sigma2 = 2 * sigma * sigma; | |
const t = 1 / (sigma * Shadow.SQRT2PI); | |
let sum = 0; | |
for (let i = 0; i < length; i++) { | |
const r = i - radius; | |
const weight = t * Math.exp(-(r * r) / sigma2); | |
sum += weight; | |
kernel[i] = weight; | |
} | |
if (sum < 1) { | |
for (let i = 0; i < length; i++) { | |
kernel[i] /= sum; | |
} | |
} | |
return kernel; | |
} | |
static normalizeColor(str) { | |
const colors = []; | |
const alphaIdx = 3; | |
if (/^#/.test(str)) { | |
(str.length === 4 ? str.replace(/[0-9a-f]/ig, '$&$&') : str) | |
.match(/[0-9a-f]{2}/ig).forEach(hex => { | |
colors.push(parseInt(hex, 16)); | |
}); | |
} | |
if (/^rgba?/.test(str)) { | |
str.match(/0?\.\d*|\d{1,3}/g).forEach((val, idx) => { | |
colors.push((idx === alphaIdx ? val * 255 : val) | 0); | |
}); | |
} | |
if (colors[alphaIdx] === undefined) { | |
colors[alphaIdx] = 255; | |
} | |
return colors; | |
} | |
constructor(width, height, color) { | |
this.width = width; | |
this.height = height; | |
const [sr, sg, sb, sa] = Shadow.normalizeColor(color); | |
this.colors = { sr, sg, sb, sa }; | |
} | |
isOutOfBlur(x, y, radius) { | |
const { | |
width, | |
height, | |
} = this; | |
return y >= radius && y <= height - radius && | |
x >= radius && x <= width - radius; | |
} | |
iterate(radius, fn) { | |
const { | |
width, | |
height, | |
} = this; | |
for (let y = -radius; y < height + radius; y++) { | |
for (let x = -radius; x < width + radius; x++) { | |
fn(x, y); | |
} | |
} | |
} | |
create(radius) { | |
const { | |
width, | |
height, | |
colors, | |
} = this; | |
const rowSize = width + radius * 2; | |
const colSize = height + radius * 2; | |
const imageData = new ImageData(rowSize, colSize); | |
const kernel = Shadow.getKernel1D(radius); | |
const temp = new Uint16Array(rowSize * colSize); | |
const { length } = kernel; | |
this.iterate(radius, (x, y) => { | |
let weight = 0; | |
if (this.isOutOfBlur(x, y, radius)) { | |
weight = 1; | |
} else { | |
const j0 = Math.max(0, radius - x); | |
const mj = Math.min(length, radius + width - x); | |
for (let j = j0; j < mj; j++) { | |
weight += kernel[j]; | |
} | |
} | |
const offset = rowSize * (y + radius) + (x + radius); | |
temp[offset] = colors.sa * weight; | |
}); | |
this.iterate(radius, (x, y) => { | |
const tempOffset = rowSize * (y + radius) + (x + radius); | |
let alpha = 0; | |
if (this.isOutOfBlur(x, y, radius)) { | |
alpha = temp[tempOffset]; | |
} else { | |
const i0 = Math.max(0, radius - y); | |
const mi = Math.min(length, radius + height - y); | |
for (let i = i0; i < mi; i++) { | |
alpha += temp[tempOffset - (radius - i) * rowSize] * kernel[i]; | |
} | |
} | |
const offset = tempOffset * 4; | |
imageData.data[offset] = colors.sr; | |
imageData.data[offset + 1] = colors.sg; | |
imageData.data[offset + 2] = colors.sb; | |
imageData.data[offset + 3] = alpha; | |
}); | |
return imageData; | |
} | |
} | |
const canvas = document.createElement('canvas'); | |
const ctx = canvas.getContext('2d'); | |
canvas.width = canvas.height = 500; | |
console.time(`blur ${radius}px`); | |
const shadow = new Shadow(width, height, color); | |
ctx.putImageData(shadow.create(radius), dx + offsetX - radius, dy + offsetY - radius); | |
ctx.fillStyle = 'blue'; | |
ctx.fillRect(100, 100, width, height); | |
console.timeEnd(`blur ${radius}px`); | |
document.body.appendChild(canvas); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment