JSFiddle - React, Tailwind, and code Playground

Paint API border experiment

by Travis Almand

HTML

<div class="container">
  account 1
</div>
<div class="container">
  account 2
</div>
<div class="container">
  account 3
</div>

SCSS

.container {
  align-items: center;
  background-color: white;
  border-radius: 10px;
  box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.5);
  display: flex;
  height: 100px;
  justify-content: center;
  margin: 20px;
  position: relative;
  overflow: hidden;
  width: 300px;
  
  // you can adjust these
  --decorate-width: 14;
  --decorate-color: rgba(255,0,0,1);
  --decorate-duration: 500;
  
  // don't adjust these
  --decorate-x: 0;
  --decorate-y: 0;
  --decorate-tick: 0;
  
  &.decorate {
    background-image: paint(decorate);
  }
}

JavaScript

console.clear();

let accountContainers = document.querySelectorAll('.container');

accountContainers.forEach(container => {
  container.addEventListener('mouseover', function (e) {
    this.classList.toggle('decorate');
    
    let self = this;
    let start = performance.now();
    let rect = self.getBoundingClientRect();
    let x = e.clientX - rect.x;
    let y = e.clientY - rect.y;
    let duration = getComputedStyle(self).getPropertyValue('--decorate-duration');
    
    requestAnimationFrame(function raf(now) {
      const count = Math.floor(now - start);
      
      self.style.cssText = `--decorate-x: ${x}; --decorate-y: ${y}; --decorate-tick: ${count};`;
      
      if(count > duration) {
        return;
      }
      requestAnimationFrame(raf);
    });
  });
  container.addEventListener('mouseout', function (e) {
    this.classList.remove('decorate');
    this.style.cssText = '--decorate-tick: 0';
  })
});

CSS.paintWorklet.addModule(URL.createObjectURL(new Blob([`
  class AccountDecorator {
  static get inputProperties() { return ['background-color', '--decorate-color', '--decorate-tick', '--decorate-x', '--decorate-y', '--decorate-width', '--decorate-duration']; }
  
  paint(ctx, geom, properties) {
  	const backgroundColor = properties.get('background-color').toString();
    const color = properties.get('--decorate-color').toString();
    const x = parseFloat(properties.get('--decorate-x'));
    const y = parseFloat(properties.get('--decorate-y'));
    const width = parseFloat(properties.get('--decorate-width'));
    const duration = parseFloat(properties.get('--decorate-duration'));
    
    let tick = parseFloat(properties.get('--decorate-tick').toString());
    if(tick < 0) { tick = 0 };
    if(tick > duration) { tick = duration };
    
    //console.log("x: " + x + " y: " + y);
    
    const lineLength = (geom.width * 2) + (geom.height * 2);
    const stepsLength = lineLength / duration;
    const currentLength = stepsLength * tick;
    
   ...