Animating Registered Custom Properties

Experiments with animating registered custom properties.

by Travis Almand

HTML

<div id="panel">
  <label id="duration"><span>duration: </span><input value="1s" /></label>
  <label id="timing_function">
    <span>timing-function</span>
    <select>
      <option>linear</option>
      <option selected>ease</option>
      <option>ease-in</option>
      <option>ease-out</option>
      <option>ease-in-out</option>
      <option>cubic-bezier(0,2,1,-1)</option>
      <option>cubic-bezier(0,.5,.5,1.5)</option>
    </select>
  </label>
  
  <button id="update">update</button>
</div>

<div class="container hover"></div>

<div class="container class"></div>

<div class="container range">
  <input type="range" min="-100" max="100" step="1" value="0" />
</div>

<div class="container button">
  <button data-value="-100">-100</button>
  <button data-value="-50">-50</button>
  <button data-value="0">0</button>
  <button data-value="50">50</button>
  <button data-value="100">100</button>
</div>

<div class="container digital">
  <div class="hours"></div>
  <div class="minutes"></div>
  <div class="seconds"></div>
</div>

<div class="container analog">
  <div class="clock">
    <div class="hours"></div>
    <div class="minutes"></div>
    <div class="seconds"></div>
  </div>
</div>

SCSS

@import url('https://fonts.googleapis.com/css?family=Luckiest+Guy');

html,
body {
  color: rebeccapurple;
  font-family: 'Luckiest Guy', sans-serif;
  margin: 0;
  padding: 0;
}
body {
  --duration: 1s;
  --timingFunction: 'ease';
}

p {
  margin: 10px;
}

button {
  background-color: #fff;
  border-color: rebeccapurple;
  border-radius: 10px;
  color: rebeccapurple;
  cursor: pointer;
  font-family: 'Luckiest Guy', sans-serif;
  font-size: 20px;
  outline: none;
  padding: 5px 10px;
}

#panel {
  border: 3px solid rebeccapurple;
  margin: 20px;
  padding: 20px;
  
  label {
    align-items: center;
    display: flex;
    margin: 5px 0;
    
    span {
      margin-right: 5px;
    }
  }
  input {
    border: 1px solid rebeccapurple;
    color: rebeccapurple;
    flex-grow: 1;
    font-family: monospace;
    font-size: 20px;
    padding: 0 10px;
  }
  select {
    border: 1px solid rebeccapurple;
    color: rebeccapurple;
    flex-grow: 1;
    font-size: 20px;
  }
}

.container {
  border: 3px solid rebeccapurple;
  font-size: 40px;
  margin: 20px;
  padding: 10px;
  position: relative;
  text-align: center;
  transition-duration: var(--duration);
  transition-property: --integer;
  transition-timing-function: var(--timingFunction);
  
  &::before {
    display: block;
    font-size: 16px;
    left: 10px;
    position: absolute;
    top: 0;
  }
}

.hover {
  --integer: 0;
  counter-reset: integer var(--integer);
  
  &:hover {
    --integer: 100;
  }
  &::before {
    content: 'hover based';
  }
  &::after {
    content: counter(integer);
    //content: counter(integer, upper-roman);
    //content: counter(integer, decimal-leading-zero);
    //content: counter(integer, lower-alpha);
    display: block;
  }
}

.class {
  --integer: 0;
  counter-reset: integer var(--integer);
  
  &.active {
    --integer: 100;
  }
  &::before {
    content: 'class based (click)';
  }
  &::after {
    content: counter(integer);
    display: block;
  }
}

.range {
  --integer: 0;
 ...

JavaScript

CSS.registerProperty({
	name: '--integer',
  syntax: '<integer>',
  inherits: false,
  initialValue: '0'
});
CSS.registerProperty({
	name: '--analog',
  syntax: '<angle>',
  inherits: false,
  initialValue: '0deg'
});

document.querySelector('#update').addEventListener('click', function () {
	const duration = document.querySelector('#duration input').value;
  const timingFunction = document.querySelector('#timing_function select').value;
  
  document.body.style.cssText = `--duration: ${duration}; --timingFunction: ${timingFunction};`;
});

document.querySelector('.class').addEventListener('click', function () {
	this.classList.toggle('active');
});

document.querySelector('.range input').addEventListener('input', function () {
	this.parentElement.style.cssText = `--integer: ${this.value}`;
});

document.querySelectorAll('.button button').forEach(button => {
	button.addEventListener('click', function () {
	  this.parentElement.style.cssText = `--integer: ${parseInt(this.dataset.value)}`;
  });
});

window.setInterval(function () {
	const digital = document.querySelector('.digital');
  const date = new Date();
  
  digital.querySelector('.hours').style.cssText = `--hours: ${date.getHours()}`;
  digital.querySelector('.minutes').style.cssText = `--minutes: ${date.getMinutes()}`;
  digital.querySelector('.seconds').style.cssText = `--seconds: ${date.getSeconds()}`;
}, 1000);

window.setInterval(function () {
	const analog = document.querySelector('.analog');
  const date = new Date();
  const hoursAngle = date.getHours() * 30 - 180;
  const minutesAngle = date.getMinutes() * 6 - 180;
  const secondsAngle = date.getSeconds() * 6 - 180;
  
  analog.querySelector('.hours').style.cssText = `--analog: ${hoursAngle}deg`;
  analog.querySelector('.minutes').style.cssText = `--analog: ${minutesAngle}deg`;
  analog.querySelector('.seconds').style.cssText = `--analog: ${secondsAngle}deg`;
});