osc-css
Version:
CSS properties to animate numeric values where they are needed using oscillators and envelopes.
126 lines (100 loc) • 6.38 kB
CSS
/** ; v0.8.0 ; LICENSE: MIT Copyright (c) 2025 Nils Riedemann
___ ____ ___ ___ ____ ____
/ __`\ /',__\ /'___\ /'___\ /',__\ /',__\
/\ \L\ \/\__, `\/\ \__/ __/\ \__//\__, `\/\__, `\
\ \____/\/\____/\ \____\/\_\ \____\/\____/\/\____/
\/___/ \/___/ \/____/\/_/\/____/\/___/ \/___/
CSS properties to animate numeric values where they are needed
using oscillators and envelopes.
https://osc.style/
*/
:where(*) {
--_osc-time-unit: 1s;
--_osc-state: var(--osc-state, running);
--_osc-env-iterations: var(--osc-env-iterations, infinite);
--_osc-global-frequency: var(--osc-frequency, 1);
--_osc-sin-frequency: var(--osc-sin-frequency, var(--_osc-global-frequency));
--_osc-cos-frequency: var(--osc-cos-frequency, var(--_osc-global-frequency));
--_osc-tri-frequency: var(--osc-tri-frequency, var(--_osc-global-frequency));
--_osc-saw-frequency: var(--osc-saw-frequency, var(--_osc-global-frequency));
--_osc-global-phase: var(--osc-phase, 0s);
--_osc-sin-phase: calc(var(--_osc-global-phase) + var(--osc-sin-phase, 0s));
--_osc-cos-phase: calc(var(--_osc-global-phase) + var(--osc-cos-phase, 0s));
--_osc-saw-phase: calc(var(--_osc-global-phase) + var(--osc-saw-phase, 0s));
--_osc-tri-phase: calc(var(--_osc-global-phase) + var(--osc-tri-phase, 0s));
/* TODO: feat: add iteration-delay (post envelope delay, poc: https://codepen.io/nocksock/pen/rNEPMjJ) */
--_osc-env-delay: var(--env-delay, 0);
--_osc-env-attack: var(--env-attack, 0);
--_osc-env-hold: var(--env-hold, 0);
--_osc-env-decay: var(--env-decay, 1);
--_osc-env-total: calc(var(--_osc-env-delay) + var(--_osc-env-attack) + var(--_osc-env-hold) + var(--_osc-env-decay));
--_osc-delay-start: calc(var(--_osc-env-delay) / var(--_osc-env-total) * 100%);
--_osc-attack-start: calc((var(--_osc-env-delay) + var(--_osc-env-attack)) / var(--_osc-env-total) * 100%);
--_osc-hold-start: calc((var(--_osc-env-attack) + var(--_osc-env-hold)) / var(--_osc-env-total) * 100%);
--_osc-decay-start: calc(var(--_osc-hold-start));
--env-dahd: linear(0 0%,
0 var(--_osc-delay-start),
1 var(--_osc-attack-start),
1 var(--_osc-hold-start),
1 var(--_osc-decay-start),
0);
--_osc-envelope: var(--env, var(--env-dahd));
/* TODO: add pulse wave (maybe w/ slope parameter?) */
--osc-only-sin: calc(1s / var(--_osc-sin-frequency)) linear var(--_osc-sin-phase) infinite osc-sin var(--_osc-state);
--osc-only-cos: calc(1s / var(--_osc-cos-frequency)) linear var(--_osc-cos-phase) infinite osc-cos var(--_osc-state);
--osc-only-saw: calc(1s / var(--_osc-saw-frequency)) linear var(--_osc-saw-phase) infinite osc-saw var(--_osc-state);
--osc-only-tri: calc(1s / var(--_osc-tri-frequency)) linear var(--_osc-tri-phase) alternate infinite osc-tri var(--_osc-state);
--osc-only-envelope: calc(var(--_osc-env-total) * var(--_osc-time-unit)) var(--_osc-envelope) var(--_osc-global-phase) forwards var(--_osc-env-iterations) env-amp var(--_osc-state);
--osc:
var(--osc-only-envelope),
var(--osc-only-sin),
var(--osc-only-cos),
var(--osc-only-saw),
var(--osc-only-tri);
--osc-sin-y:sin(var(--osc-_sin) * pi);
--osc-sin-abs: max(var(--osc-sin-y), -1 * var(--osc-sin-y));
--osc-sin-post-gain: pow(var(--osc-sin-abs), var(--amp-gain)) * var(--osc-sin-y);
--osc-sin: calc(var(--osc-sin-post-gain) * var(--amp-volume));
--osc-cos-y:cos(var(--osc-_cos) * pi);
--osc-cos-abs: max(var(--osc-cos-y), -1 * var(--osc-cos-y));
--osc-cos-post-gain: pow(var(--osc-cos-abs), var(--amp-gain)) * var(--osc-cos-y);
--osc-cos: calc(var(--osc-cos-post-gain) * var(--amp-volume));
--osc-tri-y: var(--osc-_tri);
--osc-tri-abs: max(var(--osc-tri-y), -1 * var(--osc-tri-y));
--osc-tri-post-gain: pow(var(--osc-tri-abs), var(--amp-gain)) * var(--osc-tri-y);
--osc-tri: calc(var(--osc-tri-post-gain) * var(--amp-volume));
--osc-saw-y: var(--osc-_saw);
--osc-saw-abs: max(var(--osc-saw-y), -1 * var(--osc-saw-y));
--osc-saw-post-gain: pow(var(--osc-saw-abs), var(--amp-gain)) * var(--osc-saw-y);
--osc-saw: calc(var(--osc-saw-post-gain) * var(--amp-volume));
/* - normalised oscillators - */
--osc-SIN: calc((1 + var(--osc-sin)) / 2);
--osc-COS: calc((1 + var(--osc-cos)) / 2);
--osc-SAW: calc((1 + var(--osc-saw)) / 2);
--osc-TRI: calc((1 + var(--osc-tri)) / 2);
}
/* - wave forms - */
/* TODO: reconsider implications of inherits true vs false */
@property --osc-_sin { syntax: "<number>" ; initial-value: 0 ; inherits: false ; }
@property --osc-_cos { syntax: "<number>" ; initial-value: 0 ; inherits: false ; }
@property --osc-_tri { syntax: "<number>" ; initial-value: 0 ; inherits: false ; }
@property --osc-_saw { syntax: "<number>" ; initial-value: 0 ; inherits: false ; }
@keyframes osc-sin { from { --osc-_sin: -1; } to { --osc-_sin: 1; } }
@keyframes osc-cos { from { --osc-_cos: -1; } to { --osc-_cos: 1; } }
@keyframes osc-tri { from { --osc-_tri: -1; } to { --osc-_tri: 1; } }
@keyframes osc-saw { from { --osc-_saw: -1; } to { --osc-_saw: 1; } }
/* - amp envelope - */
@property --env-amp { syntax: "<number>" ; initial-value: 0 ; inherits: false ; }
@keyframes env-amp { from { --env-amp: 0; } to { --env-amp: 1; } }
@property --_osc-env-total { syntax: "<number>" ; initial-value: 0 ; inherits: false ; }
@property --_osc-env-delay { syntax: "<number>" ; initial-value: 0 ; inherits: false ; }
@property --_osc-env-attack { syntax: "<number>" ; initial-value: 0 ; inherits: false ; }
@property --_osc-env-hold { syntax: "<number>" ; initial-value: 0 ; inherits: false ; }
@property --_osc-env-decay { syntax: "<number>" ; initial-value: 1 ; inherits: false ; }
@property --_osc-delay-start { syntax: "<percent>" ; initial-value: 0 ; inherits: false ; }
@property --_osc-attack-start { syntax: "<percent>" ; initial-value: 0 ; inherits: false ; }
@property --_osc-hold-start { syntax: "<percent>" ; initial-value: 0 ; inherits: false ; }
@property --_osc-decay-start { syntax: "<percent>" ; initial-value: 0 ; inherits: false ; }
@property --amp-volume { syntax: "<number>" ; initial-value: 1 ; inherits: false ; }
@property --amp-gain { syntax: "<number>" ; initial-value: 0 ; inherits: false ; }
/* TODO: declare any missing properties (also makes debugging easier) */