Using Registered CSS Custom Properties and the Paint API

Paint API experiment with using transition for animation support.

by Travis Almand

HTML

<h2>Custom: color, width, duration</h2>
<pre>
--paint-outline-color: rebeccapurple;
--paint-outline-duration: 2s;
--paint-outline-width: 20;
</pre>
<div class="container" data-paint="outline"></div>

<h2>Default: no custom properties</h2>
<div class="container2" data-paint="outline"></div>

SCSS

html {
  box-sizing: border-box;
}
*, *:before, *:after {
  box-sizing: inherit;
}
html,body {
  margin: 0;
  padding: 0;
}

.container {
  --paint-outline-color: rebeccapurple;
  --paint-outline-duration: 2s;
  --paint-outline-width: 20;
  border: 3px solid rebeccapurple;
  height: 200px;
  margin: 50px auto;
  transition-timing-function: ease;
  //transition-timing-function: ease-in;
  //transition-timing-function: ease-out;
  //transition-timing-function: ease-in-out;
  //transition-timing-function: linear;
  //transition-timing-function: cubic-bezier(.5,1,1,0);
  //transition-timing-function: steps(2, jump-end);
  width: 200px;
  
  &:hover {
    //--paint-outline-color: green;
    //--paint-outline-width: 20;
  }
}

.container2 {
  background-color: gainsboro;
  height: 200px;
  margin: 50px auto;
  width: 200px;
}

[data-paint="outline"] {
  --paint-outline-tick: 0;
  position: relative;
}
[data-paint="outline"]:hover {
  background-image: paint(paint-outline);
  transition-duration: var(--paint-outline-duration);
  transition-property: --paint-outline-tick, --paint-outline-color, --paint-outline-width;
}
[data-paint="outline"] .paintOutline-hitbox {
  height: 100%;
  left: 0;
  position: absolute;
  top: 0;
  width: 100%;
}
[data-paint="outline"] .paintOutline-hitbox:hover {
  clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%);
  z-index: 2;
}
[data-paint-side="0"] {
  clip-path: polygon(0% 0%, 100% 0%, 50% 50%);
}
[data-paint-side="1"] {
  clip-path: polygon(100% 0%, 100% 100%, 50% 50%);
}
[data-paint-side="2"] {
  clip-path: polygon(0% 100%, 100% 100%, 50% 50%);
}
[data-paint-side="3"] {
  clip-path: polygon(0% 0%, 0% 100%, 50% 50%);
}

JavaScript

function paintOutlineInit (outline) {
	const paintOutlineMouseover = e => {
		const target = e.target;
    
    if (target.classList.contains('paintOutline-hitbox')) {
    	const parent = target.parentElement;
    	const duration = parseFloat(window.getComputedStyle(parent).getPropertyValue('--paint-outline-duration')) * 1000;
      
    	let side = parseInt(target.dataset.paintSide);
      let x = e.clientX - parent.offsetLeft;
      let y = e.clientY - parent.offsetTop;
      
      x = side === 3 ? 0 : x;
      x = side === 1 ? target.offsetWidth : x;
      y = side === 0 ? 0 : y;
      y = side === 2 ? target.offsetWidth : y;
      
      parent.style.cssText = `--paint-outline-tick: ${duration}; --paint-outline-x: ${x}; --paint-outline-y: ${y}; --paint-outline-side: ${side};`;
    }
	}
  const paintOutlineMouseout = e => {
  	const target = e.target;
    const parent = target.parentElement;
    
  	parent.style.cssText = '--paint-outline-tick: 0;';
  }

  outline.insertAdjacentHTML('beforeend', '<div class="paintOutline-hitbox" data-paint-side="0"></div><div class="paintOutline-hitbox" data-paint-side="1"></div><div class="paintOutline-hitbox" data-paint-side="2"></div><div class="paintOutline-hitbox" data-paint-side="3"></div>');
  
  outline.addEventListener('mouseover', paintOutlineMouseover);
  outline.addEventListener('mouseout', paintOutlineMouseout);
}

CSS.registerProperty({
	name: '--paint-outline-color',
  syntax: '<color>',
  inherits: false,
  initialValue: 'rgba(0, 0, 0, 1)'
});
CSS.registerProperty({
	name: '--paint-outline-width',
  syntax: '<integer>',
  inherits: false,
  initialValue: '5'
});
CSS.registerProperty({
	name: '--paint-outline-duration',
  syntax: '<time>',
  inherits: false,
  initialValue: '500ms'
});
CSS.registerProperty({
  name: '--paint-outline-tick',
  syntax: '<integer>',
  inherits: false,
  initialValue: '0'
});

CSS.paintWorklet.addModule(URL.createObjectURL(new Blob([`
class PaintOutline {
  static get...