<div class="no-dice" hidden>
You need <a href="https://drafts.css-houdini.org/css-paint-api/"><code>PaintWorklet</code></a> to see this demo :(
</div>
<button type="button">Hello</button>
<script type="houdini/worklet">
registerPaint('ripple', class {
static get inputProperties() {
return [
'--ripple-color',
'--ripple-radius',
'--ripple-center-x',
'--ripple-center-y'
];
}
paint(ctx, { width, height }, props) {
const color = props.get('--ripple-color');
const radius = props.get('--ripple-radius');
const centerX = props.get('--ripple-center-x');
const centerY = props.get('--ripple-center-y');
const farthest = (Math.max(centerX, width - centerX) ** 2
+ Math.max(centerY, height - centerY) ** 2) ** .5;
ctx.fillStyle = color;
ctx.arc(centerX.value, centerY.value, radius.value * farthest / 100, 0, Math.PI * 2);
ctx.fill();
}
});
</script>
html, body {
height: 100%;
}
body {
margin: 0;
font-family: system-ui, sans-serif;
display: flex;
justify-content: center;
align-items: center;
}
button {
border: none;
border-radius: .25em;
font-size: 4em;
width: 10em;
padding: .25em 0;
background-image: paint(ripple);
background-color: red;
color: white;
}
View Compiled
(async () => {
if (typeof CSS === 'undefined' || !('paintWorklet' in CSS)) {
document.querySelector('.no-dice').hidden = false;
return;
}
CSS.registerProperty({
name: '--ripple-color',
syntax: '<color>',
initialValue: 'rgba(255, 255, 255, 0.5)',
inherits: false
});
CSS.registerProperty({
name: '--ripple-center-x',
syntax: '<number>',
initialValue: 0,
inherits: false
});
CSS.registerProperty({
name: '--ripple-center-y',
syntax: '<number>',
initialValue: 0,
inherits: false
});
CSS.registerProperty({
name: '--ripple-radius',
syntax: '<percentage>',
initialValue: '0%',
inherits: false
});
const houdiniModule = URL.createObjectURL(new Blob(
[ document.querySelector('[type="houdini/worklet"]').textContent ],
{ type: "text/javascript" }
));
await CSS.paintWorklet.addModule(houdiniModule);
const button = document.querySelector('button');
button.addEventListener('click', ({ clientX, clientY }) => {
const { top, left } = button.getBoundingClientRect();
const offsetX = clientX - left;
const offsetY = clientY - top;
// It used to accept `CSS.number(offsetX)` rather than just `offsetX`, but
// now... it doesn't.
button.attributeStyleMap.set('--ripple-center-x', offsetX);
button.attributeStyleMap.set('--ripple-center-y', offsetY);
button.animate([
{
'--ripple-color': 'rgba(255, 255, 255, 0.5)',
'--ripple-radius': CSS.percent(0)
},
{
'--ripple-color': 'rgba(255, 255, 255, 0)',
'--ripple-radius': CSS.percent(100)
}
], {
duration: 1000
});
});
})();
This Pen doesn't use any external JavaScript resources.