diff --git a/build/to-stylesheet.js b/build/to-stylesheet.js index 2d0afd51..557a2d6b 100644 --- a/build/to-stylesheet.js +++ b/build/to-stylesheet.js @@ -1,5 +1,20 @@ import fs from 'fs' +/** + * Wraps a set of definitions inside of a media query + * @param {*} queryValue The media value to query for + * @param {*} definitions The definitions that need to be wrapped + * @returns Media query string + */ +const wrapInQuery = (queryValue, definitions) => { + return definitions ? ` +@media (${queryValue}) { + ${definitions.reduce((acc, [_, val], i) => ( + `${acc} ${i ? `\n` : ''} ${val}` + ), '')} +}` : ''; +} + export const buildPropsStylesheet = ({filename,props}, {selector,prefix}) => { const file = fs.createWriteStream("../src/" + filename) @@ -53,19 +68,24 @@ ${dark_propsMeta} }) if (filename.includes('animations')) { - let dark_props = Object.entries(props) - .filter(([prop, val]) => - prop.includes('-@media:dark')) - dark_props.forEach(([prop, val], index) => { - let hasDarkKeyframe = prop.endsWith('-@media:dark') && val.trim().startsWith('@keyframe') - if (hasDarkKeyframe) { - appendedMeta += ` -@media (--OSdark) { - ${val.trim().replace(/\n/g, '\n ')}; -}` + const [ + dark_props, + reduced_props, + ] = Object.entries(props).reduce((acc, prop) => { + const [key, val] = prop; + + if (val.trim().startsWith('@keyframe')) { + const _val = val.trim().replace(/\n/g, '\n '); + key.endsWith('-@media:dark') && acc[0].push([key, _val]); + key.endsWith('-@media:reduced') && acc[1].push([key, _val]); } - }) + + return acc; + }, [[], []]); + + appendedMeta += wrapInQuery('--OSdark', dark_props) + appendedMeta += wrapInQuery('--motionNotOK', reduced_props) } file.write('}\n') diff --git a/docsite/index.html b/docsite/index.html index 8286348e..f977899b 100644 --- a/docsite/index.html +++ b/docsite/index.html @@ -2413,6 +2413,7 @@
Ease Steps

Animations

Premade keyframe effects allow you to orchestrate your own animations. Plus, a few nice attention-grabbers and indeterminate state animations.

+

All animations automatically offer a reduced motion fallback. Slide and scale animations crossfade between their start and end states, while all the attention getting animations reduce to a simple attention getting blink.

@@ -2423,6 +2424,7 @@
The Props
--animation-fade-{in,out}-bloom --animation-shake-{x,y} + --animation-scale-{up,down} --animation-slide-out-{up,down,left,right} --animation-slide-in-{up,down,left,right} diff --git a/src/props.animations.css b/src/props.animations.css index 603555a9..08ecdae3 100644 --- a/src/props.animations.css +++ b/src/props.animations.css @@ -126,4 +126,258 @@ 10% { opacity: 1; filter: brightness(0.5) blur(10px) } 0% { opacity: 1; filter: brightness(1) blur(0) } }; +} +@media (--motionNotOK) { + @keyframes scale-up { + 0% { + opacity: 1; + } + 50% { + opacity: 0; + transform: scale(1); + } + 50.01% { + transform: scale(1.25); + } + 100% { + opacity: 1; + transform: scale(1.25); + } + }; +} +@media (--motionNotOK) { + @keyframes scale-down { + 0% { + opacity: 1; + } + 50% { + opacity: 0; + transform: scale(1); + } + 50.01% { + transform: scale(0.75); + } + 100% { + opacity: 1; + transform: scale(0.75); + } + }; +} +@media (--motionNotOK) { + @keyframes slide-out-up { + 0% { + opacity: 1; + } + 50% { + opacity: 0; + transform: translateY(0%); + } + 50.01% { + transform: translateY(-100%); + } + 100% { + opacity: 1; + transform: translateY(-100%); + } + }; +} +@media (--motionNotOK) { + @keyframes slide-out-down { + 0% { + opacity: 1; + } + 50% { + opacity: 0; + transform: translateY(0%); + } + 50.01% { + transform: translateY(100%); + } + 100% { + opacity: 1; + transform: translateY(100%); + } + }; +} +@media (--motionNotOK) { + @keyframes slide-out-right { + 0% { + opacity: 1; + } + 50% { + opacity: 0; + transform: translateX(0%); + } + 50.01% { + transform: translateX(100%); + } + 100% { + opacity: 1; + transform: translateX(100%); + } + }; +} +@media (--motionNotOK) { + @keyframes slide-out-left { + 0% { + opacity: 1; + } + 50% { + opacity: 0; + transform: translateX(0%); + } + 50.01% { + transform: translateX(-100%); + } + 100% { + opacity: 1; + transform: translateX(-100%); + } + }; +} +@media (--motionNotOK) { + @keyframes slide-in-up { + 0% { + opacity: 1; + transform: translateY(100%); + } + 50% { + opacity: 0; + transform: translateY(100%); + } + 50.01% { + transform: translateY(0%); + } + 100% { + opacity: 1; + transform: translateY(0%); + } + }; +} +@media (--motionNotOK) { + @keyframes slide-in-down { + 0% { + opacity: 1; + transform: translateY(-100%); + } + 50% { + opacity: 0; + transform: translateY(-100%); + } + 50.01% { + transform: translateY(0%); + } + 100% { + opacity: 1; + transform: translateY(0%); + } + }; +} +@media (--motionNotOK) { + @keyframes slide-in-right { + 0% { + opacity: 1; + transform: translateX(-100%); + } + 50% { + opacity: 0; + transform: translateX(-100%); + } + 50.01% { + transform: translateX(0%); + } + 100% { + opacity: 1; + transform: translateX(0%); + } + }; +} +@media (--motionNotOK) { + @keyframes slide-in-left { + 0% { + opacity: 1; + transform: translateX(100%); + } + 50% { + opacity: 0; + transform: translateX(100%); + } + 50.01% { + transform: translateX(0%); + } + 100% { + opacity: 1; + transform: translateX(0%); + } + }; +} +@media (--motionNotOK) { + @keyframes shake-x { + 0%, 100% { + opacity: 1 + } + 50% { + opacity: .5 + } + }; +} +@media (--motionNotOK) { + @keyframes shake-y { + 0%, 100% { + opacity: 1 + } + 50% { + opacity: .5 + } + }; +} +@media (--motionNotOK) { + @keyframes spin { + 0%, 100% { + opacity: 1 + } + 50% { + opacity: .5 + } + }; +} +@media (--motionNotOK) { + @keyframes ping { + 0%, 100% { + opacity: 1 + } + 50% { + opacity: .5 + } + }; +} +@media (--motionNotOK) { + @keyframes float { + 0%, 100% { + opacity: 1 + } + 50% { + opacity: .5 + } + }; +} +@media (--motionNotOK) { + @keyframes bounce { + 0%, 100% { + opacity: 1 + } + 50% { + opacity: .5 + } + }; +} +@media (--motionNotOK) { + @keyframes pulse { + 0%, 100% { + opacity: 1 + } + 50% { + opacity: .5 + } + }; } \ No newline at end of file diff --git a/src/props.animations.js b/src/props.animations.js index 5ba7d050..a01721d8 100644 --- a/src/props.animations.js +++ b/src/props.animations.js @@ -1,3 +1,8 @@ +const LOCAL_REFERENCES = { + "--animation-blink-@": `0%, 100% { opacity: 1 } + 50% { opacity: .5 }` +}; + export default { "--animation-fade-in": "fade-in .5s var(--ease-3)", "--animation-fade-in-@": ` @@ -39,51 +44,121 @@ export default { "--animation-scale-up-@": ` @keyframes scale-up { to { transform: scale(1.25) } +}`, +"--animation-scale-up-@media:reduced": ` +@keyframes scale-up { + 0% { opacity: 1; } + 50% { opacity: 0; transform: scale(1); } + 50.01% { transform: scale(1.25); } + 100% { opacity: 1; transform: scale(1.25); } }`, "--animation-scale-down": "scale-down .5s var(--ease-3)", "--animation-scale-down-@": ` @keyframes scale-down { to { transform: scale(.75) } +}`, +"--animation-scale-down-@media:reduced": ` +@keyframes scale-down { + 0% { opacity: 1; } + 50% { opacity: 0; transform: scale(1); } + 50.01% { transform: scale(.75); } + 100% { opacity: 1; transform: scale(.75); } }`, "--animation-slide-out-up": "slide-out-up .5s var(--ease-3)", "--animation-slide-out-up-@": ` @keyframes slide-out-up { to { transform: translateY(-100%) } +}`, +"--animation-slide-out-up-@media:reduced": ` +@keyframes slide-out-up { + 0% { opacity: 1; } + 50% { opacity: 0; transform: translateY(0%); } + 50.01% { transform: translateY(-100%); } + 100% { opacity: 1; transform: translateY(-100%); } }`, "--animation-slide-out-down": "slide-out-down .5s var(--ease-3)", "--animation-slide-out-down-@": ` @keyframes slide-out-down { to { transform: translateY(100%) } +}`, +"--animation-slide-out-down-@media:reduced": ` +@keyframes slide-out-down { + 0% { opacity: 1; } + 50% { opacity: 0; transform: translateY(0%); } + 50.01% { transform: translateY(100%); } + 100% { opacity: 1; transform: translateY(100%); } }`, "--animation-slide-out-right": "slide-out-right .5s var(--ease-3)", "--animation-slide-out-right-@": ` @keyframes slide-out-right { to { transform: translateX(100%) } +}`, +"--animation-slide-out-right-@media:reduced": ` +@keyframes slide-out-right { + 0% { opacity: 1; } + 50% { opacity: 0; transform: translateX(0%); } + 50.01% { transform: translateX(100%); } + 100% { opacity: 1; transform: translateX(100%); } }`, "--animation-slide-out-left": "slide-out-left .5s var(--ease-3)", "--animation-slide-out-left-@": ` @keyframes slide-out-left { to { transform: translateX(-100%) } +}`, +"--animation-slide-out-left-@media:reduced": ` +@keyframes slide-out-left { + 0% { opacity: 1; } + 50% { opacity: 0; transform: translateX(0%); } + 50.01% { transform: translateX(-100%); } + 100% { opacity: 1; transform: translateX(-100%); } }`, "--animation-slide-in-up": "slide-in-up .5s var(--ease-3)", "--animation-slide-in-up-@": ` @keyframes slide-in-up { from { transform: translateY(100%) } +}`, +"--animation-slide-in-up-@media:reduced": ` +@keyframes slide-in-up { + 0% { opacity: 1; transform: translateY(100%); } + 50% { opacity: 0; transform: translateY(100%); } + 50.01% { transform: translateY(0%); } + 100% { opacity: 1; transform: translateY(0%); } }`, "--animation-slide-in-down": "slide-in-down .5s var(--ease-3)", "--animation-slide-in-down-@": ` @keyframes slide-in-down { from { transform: translateY(-100%) } +}`, +"--animation-slide-in-down-@media:reduced": ` +@keyframes slide-in-down { + 0% { opacity: 1; transform: translateY(-100%); } + 50% { opacity: 0; transform: translateY(-100%); } + 50.01% { transform: translateY(0%); } + 100% { opacity: 1; transform: translateY(0%); } }`, "--animation-slide-in-right": "slide-in-right .5s var(--ease-3)", "--animation-slide-in-right-@": ` @keyframes slide-in-right { from { transform: translateX(-100%) } +}`, +"--animation-slide-in-right-@media:reduced": ` +@keyframes slide-in-right { + 0% { opacity: 1; transform: translateX(-100%); } + 50% { opacity: 0; transform: translateX(-100%); } + 50.01% { transform: translateX(0%); } + 100% { opacity: 1; transform: translateX(0%); } }`, "--animation-slide-in-left": "slide-in-left .5s var(--ease-3)", "--animation-slide-in-left-@": ` @keyframes slide-in-left { from { transform: translateX(100%) } +}`, +"--animation-slide-in-left-@media:reduced": ` +@keyframes slide-in-left { + 0% { opacity: 1; transform: translateX(100%); } + 50% { opacity: 0; transform: translateX(100%); } + 50.01% { transform: translateX(0%); } + 100% { opacity: 1; transform: translateX(0%); } }`, "--animation-shake-x": "shake-x .75s var(--ease-out-5)", "--animation-shake-x-@": ` @@ -93,6 +168,10 @@ export default { 40% { transform: translateX(5%) } 60% { transform: translateX(-5%) } 80% { transform: translateX(5%) } +}`, +"--animation-shake-x-@media:reduced": ` +@keyframes shake-x { + ${LOCAL_REFERENCES['--animation-blink-@']} }`, "--animation-shake-y": "shake-y .75s var(--ease-out-5)", "--animation-shake-y-@": ` @@ -102,11 +181,19 @@ export default { 40% { transform: translateY(5%) } 60% { transform: translateY(-5%) } 80% { transform: translateY(5%) } +}`, +"--animation-shake-y-@media:reduced": ` +@keyframes shake-y { + ${LOCAL_REFERENCES['--animation-blink-@']} }`, "--animation-spin": "spin 2s linear infinite", "--animation-spin-@": ` @keyframes spin { to { transform: rotate(1turn) } +}`, +"--animation-spin-@media:reduced": ` +@keyframes spin { + ${LOCAL_REFERENCES['--animation-blink-@']} }`, "--animation-ping": "ping 5s var(--ease-out-3) infinite", "--animation-ping-@": ` @@ -116,20 +203,23 @@ export default { opacity: 0; } }`, - "--animation-blink": "blink 1s var(--ease-out-3) infinite", - "--animation-blink-@": ` -@keyframes blink { - 0%, 100% { - opacity: 1 - } - 50% { - opacity: .5 - } +"--animation-ping-@media:reduced": ` +@keyframes ping { + ${LOCAL_REFERENCES['--animation-blink-@']} }`, + "--animation-blink": "blink 1s var(--ease-out-3) infinite", + "--animation-blink-@": ` + @keyframes blink { + ${LOCAL_REFERENCES['--animation-blink-@']} + }`, "--animation-float": "float 3s var(--ease-in-out-3) infinite", "--animation-float-@": ` @keyframes float { 50% { transform: translateY(-25%) } +}`, +"--animation-float-@media:reduced": ` +@keyframes float { + ${LOCAL_REFERENCES['--animation-blink-@']} }`, "--animation-bounce": "bounce 2s var(--ease-squish-2) infinite", "--animation-bounce-@": ` @@ -137,10 +227,18 @@ export default { 25% { transform: translateY(-20%) } 40% { transform: translateY(-3%) } 0%, 60%, 100% { transform: translateY(0) } +}`, +"--animation-bounce-@media:reduced": ` +@keyframes bounce { + ${LOCAL_REFERENCES['--animation-blink-@']} }`, "--animation-pulse": "pulse 2s var(--ease-out-3) infinite", "--animation-pulse-@": ` @keyframes pulse { 50% { transform: scale(.9,.9) } }`, +"--animation-pulse-@media:reduced": ` +@keyframes pulse { + ${LOCAL_REFERENCES['--animation-blink-@']} +}`, }