Animation Toggle v2.9.0
Warning: Individuals with vestibular motion disorders may wish to activate the "reduce motion" feature on their device prior to engaging with animations in this demo.
Allow users to pause or resume web animations, videos, or other dynamic content for improved control and accessibility.
<div class="animation-toggle">
<video class="animation-toggle__video" loading="lazy">
<source src="https://radancy.dev/component-library/animation-toggle/demo-lg.mp4">
</video>
</div>/*!
Radancy Component Library: Animation Toggle
Contributor(s):
Michael "Spell" Spellacy
Dependencies: Sass
*/
.animation-toggle {
display: grid;
// Control Container (Mandatory)
&__controls {
display: grid;
gap: calc(12rem / 16);
grid-auto-flow: column;
justify-self: end;
margin-block-start: calc(12rem / 16);
margin-inline-end: calc(12rem / 16);
z-index: 2;
}
// Toggle Buttons (Mandatory)
// Important: Be sure to adjust colors as needed so that button stands out on darker or lighter backgrounds.
&__pause, &__audio {
background-color: #000;
background-position: center;
background-repeat: no-repeat;
border-radius: calc(3em / 16);
border: calc(1rem / 16) #fff solid;
cursor: pointer;
height: calc(30rem / 16);
opacity: 0.5;
outline-offset: 0.25em;
transition: all 300ms ease-in-out;
width: calc(30rem / 16);
.animation-toggle:focus-within &, .animation-toggle:hover & {
opacity: 1;
}
}
&__pause {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAMAAAAp4XiDAAAAclBMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////9eWEHEAAAAJXRSTlMA8mB05+Tr4NqonE0f7si8sJWTdXNlXEk6MSTd0qKKf1UnFxEL+BREzAAAAKRJREFUSMftlscOg0AMBR/E9E5CTS/7/78YpA1klbUiwdlzfKM52wAedZjRjE/5DQZdQf5XHuMUE736JcJCZckESB1lUc3FVdkMqBVDqouXz7gIIZe4Okk4F8Dh5oNOXM45fLJfn+wkkUQSSSSRRJLt53XLEQ+5ufn7KsTcPOrkmTGuxJ1ZY3y4MLIHOmsssHC2ZIuJMQrImyE6NTBoczKkVw7AG83JGMcGlBNJAAAAAElFTkSuQmCC");
background-size: 0.625rem;
&[aria-pressed="true"] {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOoAAAE2AQMAAABV0ZztAAAABlBMVEUAAAD///+l2Z/dAAAAAXRSTlMAQObYZgAAAipJREFUaN7t2rFNBjEMhuFDFJSM8I/CaHg0j+IRXLqw/CEQBc29KSyoSPtId7kUucT+rutZccF4qInfJAOW6OlPkuqenyV6+Ysk3fOrRHN7fLIzJ322VMxzy++frAMbcwDT1L9UxTzMOrAxB3MyN/Mw68DOHMzJ3MzDrAM7czAXczMPs4zZmYO5mJt5mGXMzpzMxdzMOrAxO3MyF/Mw68DGHMzJXMzDrAMbczAnczMPsw7szMGczM08zDqwMwdzMTfzMMuYnTmYi7mZdWBjduZkLuZm1oGN2ZmTuZiHWQc25mBO5mIeZh3YmIM5mZt5mHVgZw7mZG7mYZYxO3MwF3MzD7OM2ZmDuZibWQc2ZmdO5mJuZh3YmJ05mYt5mHVgYw7mYm7mYZYxO3N8881I5mLuFQ+zdmy/yf7Pf77m14aHuVdczMkczM5szKutp5iT2Zlts2E3czEHs2/+gsNczMkczMa8OrcUczA7syHP6qyYzMFszKsDeDEnszMb8qwuPckczMa8uioWczI7s23u381czMHsm6rFMBdzMjuzMa/qTMUczM68qs41czIHszGvCqbFHMzObMizKlInczAb86q0X8zJ7My26Zc0czIHs2+6TMNczMnszIY8q75gMQezM6+6qcWczMFszKsGd1FiQQpmZ76Qh7mZkzkoBiIZ84Xcd/z4uaR3nMyO4RkZ8nAyp5kTYz8KDA3JmDmR1JxnSuCH5MAvGtDrSXGPH9boP57gj/lMAAAAAElFTkSuQmCC")
}
&[disabled] {
background-color: #333;
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAMAAAAp4XiDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDkuMC1jMDAwIDc5LjE3MWMyN2ZhYiwgMjAyMi8wOC8xNi0yMjozNTo0MSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIDI0LjAgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjFGNDQzOTFBMDNFNzExRUZBREE3RDY3NEY5MTNEMDlEIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjFGNDQzOTFCMDNFNzExRUZBREE3RDY3NEY5MTNEMDlEIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6MUY0NDM5MTgwM0U3MTFFRkFEQTdENjc0RjkxM0QwOUQiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6MUY0NDM5MTkwM0U3MTFFRkFEQTdENjc0RjkxM0QwOUQiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7n3NecAAAABlBMVEX///////9VfPVsAAAAAnRSTlP/AOW3MEoAAADVSURBVHjarNbbDsMgDAPQ5P9/epo2qQEnthetj4WzUciFSHjieJpxOr9VIQGgcMSJwgKHCVMUE654TPSCvMxmcN71SvjRXYMhBQwHXxagL1FBVad8iAgpMA3h5k2emd4eFHKteP6bg1AzLcYxEEBWFFxxOhpNpBmCwyOzISfupA6UHfWa/p5NUnyk2J35YLTRSTLHkhNTkDGmGfKFFhYkwtRETrPqtRUmvVS0q8RZLX8xWJPTLeOLZrFpSVP1Z41v0143TXxzVeDmj9eezeXKusK9BBgAWdwE4I4jwMQAAAAASUVORK5CYII=");
cursor: default;
}
[data-pause-remove] & {
display: none;
}
}
&__audio {
background-image: url("data:image/png;base64,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");
background-size: calc(18rem / 16);
}
// Video (Optional)
&__video {
width: 100%;
}
// Backdrop (Optional)
&__backdrop {
&:not(.animation-enabled &) {
&::after {
background-color: rgba(0, 0, 0, 0.5);
content: "";
pointer-events: none;
position: relative;
z-index: 1;
}
}
}
// Primary Content (Optional)
&__content {
color: #fff;
font-size: clamp(1rem, 9vw, 6rem);
font-weight: bold;
line-height: normal;
place-self: center;
text-align: center;
z-index: 2;
}
// Audio Description Track (Optional)
&__track {
align-self: end;
background-color: rgba(0,0,0,.8);
border-radius: 0.3125em;
color: #fff;
display: none;
font-size: 0.9rem;
line-height: normal;
margin: 1em;
padding: 0.7em;
text-align: center;
z-index: 1;
@media(min-width: 42.5em) {
font-size: 1.1rem;
justify-self: center;
}
&.active {
display: block;
&:empty {
display: none;
}
}
}
// Shared
&__controls, &__video, &__content, &__track, &__backdrop::after {
grid-area: 1 / 1;
}
}
/*!
Radancy Component Library: Animation Toggle
Contributor(s):
Michael "Spell" Spellacy
Dependencies: None
*/
(() => {
"use strict";
const loadLanguagePack = (url, callback) => {
// Install Language Pack.
const getComponentLanguagePack = document.getElementById("component-library-language-pack");
if (!getComponentLanguagePack) {
const componentLanguagePack = document.createElement("script");
componentLanguagePack.setAttribute("src", url);
componentLanguagePack.setAttribute("id", "component-library-language-pack");
componentLanguagePack.addEventListener("load", callback);
document.head.appendChild(componentLanguagePack);
} else {
getComponentLanguagePack.addEventListener("load", callback);
}
};
const initAnimationToggle = () => {
loadLanguagePack("https://services.tmpwebeng.com/component-library/language-pack.js", () => {
// Display which component in use via console:
console.log("%cAnimation Toggle%cv2.9.0", "background: #2d2d2d; color: #fff; padding: 6px 10px; border-radius: 16px 0 0 16px; font-weight: 600;" , "background: #6e00ee; color: #fff; padding: 6px 10px; border-radius: 0 16px 16px 0; font-weight: 600;");
// Animation variables
const animationBody = document.body;
const atAudioDescriptionClassName = "animation-toggle__audio";
const atClass = ".animation-toggle";
const atCallback = document.querySelectorAll(`${atClass}[data-callback]`);
const atCookieName = "AnimationPaused";
const atDescriptionTrackClass = ".animation-toggle__track";
const atEnabledClassName = "animation-enabled";
const atPauseButtonClassName = "animation-toggle__pause";
const atPauseButtonClass = `.${atPauseButtonClassName}`;
const atVideoClass = ".animation-toggle__video";
const atVideoControlsName = "animation-toggle__controls";
const dataAudioDescriptionButton = "data-audio-description-button";
const dataAutoplayDisable = "data-autoplay-disable";
const dataDecorative = "data-decorative";
const dataLoop = "data-loop";
const dataPauseButton = "data-pause-button";
const dataPoster = "data-poster";
const getAnimationWrappers = document.querySelectorAll(atClass);
const getBackgroundVideos = document.querySelectorAll(atVideoClass);
// Language
const atAudioDescriptionLabel = window.atAudioDescriptionLabel;
const atPauseButtonLabel = window.atPauseButtonLabel;
const atVideoLabel = window.atVideoLabel;
// Used to retrieve cookie and pause video(s) if present.
const getCookie = name => {
const match = document.cookie.match(RegExp(`(?:^|;\\s*)${name}=([^;]*)`));
return match ? match[1] : null;
};
// Assign cookie to variable on page load.
let animationPaused = getCookie(atCookieName);
// Used to set and remove cookie.
const setCookie = state => {
document.cookie = `${atCookieName}=${state}; SameSite=None; Secure; path=/`;
};
// If animation disabled in OS settings and cookie is not present then force pause.
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
if(animationPaused === null) {
setCookie("true");
animationPaused = getCookie(atCookieName);
}
}
// For each animation wrapper...
getAnimationWrappers.forEach(wrapper => {
// Create control wrapper.
const btnControls = document.createElement("div");
btnControls.setAttribute("class", atVideoControlsName);
// Prepend control wrapper to atClass.
wrapper.prepend(btnControls);
// Create pause button
const btnPlayPause = document.createElement("button");
// See if wrapper contains custom pause button value; use over default if true.
btnPlayPause.setAttribute("aria-label", wrapper.hasAttribute(dataPauseButton) ? wrapper.getAttribute(dataPauseButton) : atPauseButtonLabel);
// Add class to pause button.
btnPlayPause.classList.add(atPauseButtonClassName);
// Check to see if cookie is false or null.
if(animationPaused === "false" || animationPaused === null) {
btnPlayPause.setAttribute("aria-pressed", "false");
animationBody.classList.add(atEnabledClassName);
} else {
btnPlayPause.setAttribute("aria-pressed", "true");
animationBody.classList.remove(atEnabledClassName);
}
// Append pause button.
btnControls.append(btnPlayPause);
// Pause Toggle Event.
btnPlayPause.addEventListener("click", function() {
const getAtPauseButtonClass = document.querySelectorAll(atPauseButtonClass);
const animationPauseToggles = getAtPauseButtonClass;
if (this.getAttribute("aria-pressed") === "false") {
animationBody.classList.remove(atEnabledClassName);
setCookie("true");
animationPauseToggles.forEach(button => {
button.setAttribute("aria-pressed", "true");
});
} else {
animationBody.classList.add(atEnabledClassName);
setCookie("false");
animationPauseToggles.forEach(button => {
button.setAttribute("aria-pressed", "false");
});
}
getBackgroundVideos.forEach(video => {
if(!video.closest(atClass).querySelector(atPauseButtonClass).hasAttribute("disabled")) {
const isPaused = video.closest(atClass).querySelector(atPauseButtonClass).getAttribute("aria-pressed");
if(isPaused === "false") {
video.play();
} else {
video.pause();
}
}
});
if(this.closest(atClass).hasAttribute("data-callback")) {
const thisButton = this;
const callBackFunction = this.closest(atClass).getAttribute("data-callback");
customCallback(thisButton, callBackFunction);
}
});
if (wrapper.querySelector("track") !== null) {
const btnAudioDescription = document.createElement("button");
btnAudioDescription.setAttribute("aria-label", wrapper.hasAttribute(dataAudioDescriptionButton) ? wrapper.getAttribute(dataAudioDescriptionButton) : atAudioDescriptionLabel);
btnAudioDescription.setAttribute("aria-pressed", "false");
btnAudioDescription.setAttribute("class", atAudioDescriptionClassName);
btnControls.append(btnAudioDescription);
btnAudioDescription.addEventListener("click", function () {
const thisVideo = wrapper.querySelector(atVideoClass);
const thisDescription = wrapper.querySelector(atDescriptionTrackClass);
thisDescription.classList.toggle("active");
const isPressed = this.getAttribute("aria-pressed") === "true";
const track = thisVideo.textTracks ? thisVideo.textTracks[0] : null;
if (!isPressed) {
this.setAttribute("aria-pressed", "true");
if (track) {
track.mode = "hidden";
track.oncuechange = function () {
const currentCue = this.activeCues[0];
if (currentCue) {
thisDescription.innerText = "";
thisDescription.appendChild(currentCue.getCueAsHTML());
const Message = thisDescription.textContent;
const msg = new SpeechSynthesisUtterance(Message);
window.speechSynthesis.speak(msg);
}
};
}
} else {
this.setAttribute("aria-pressed", "false");
if (track) {
track.oncuechange = null;
}
thisDescription.innerText = "";
window.speechSynthesis.cancel();
}
});
}
});
getBackgroundVideos.forEach((video, e) => {
video.setAttribute("crossorigin", "anonymous")
video.setAttribute("disableRemotePlayback", "");
video.setAttribute("disablePictureInPicture", "");
video.id = `animation-toggle-video-${e + 1}`;
video.setAttribute("playsinline" , "");
video.muted = true;
video.load();
if (animationBody.classList.contains(atEnabledClassName)) {
if(!video.hasAttribute(dataAutoplayDisable)) {
video.onloadeddata = () => {
const playPromise = video.play();
if (playPromise !== undefined) {
playPromise.then(() => {
// Automatic playback started!
}).catch(error => {
console.error('Playback error:', error);
});
}
}
}
} else {
if(video.hasAttribute(dataPoster)) {
video.currentTime = video.getAttribute(dataPoster);
}
}
if(video.hasAttribute(dataLoop)) {
let iterations = 1;
video.addEventListener("ended", () => {
if (iterations < video.getAttribute(dataLoop)) {
video.currentTime = 0;
video.play();
iterations ++;
} else {
const toggleButton = video.closest(atClass).querySelector(atPauseButtonClass);
if(toggleButton) {
toggleButton.setAttribute("disabled", "");
}
}
}, false);
} else {
video.setAttribute("loop" , "");
}
if(!video.hasAttribute("aria-label")) {
video.setAttribute("aria-label", atVideoLabel);
}
if(video.hasAttribute(dataDecorative)) {
video.setAttribute("aria-hidden", "true");
video.setAttribute("tabindex", "-1");
video.removeAttribute("aria-label");
}
});
const customCallback = (thisButton, customCallBackName) => {
if (customCallBackName !== null) {
window[customCallBackName](thisButton);
}
};
atCallback.forEach(callback => {
const thisButton = callback.querySelector(atPauseButtonClass);
const callBackFunction = callback.dataset.callback;
customCallback(thisButton, callBackFunction);
});
});
};
initAnimationToggle();
})();<div class="animation-toggle" data-callback="toggleAfterEffects">
<div class="callback-example" data-animation-path="https://radancy.dev/component-library/animation-toggle/callback-animation.json" data-animation-pause="110"></div>
</div>/*!
Radancy Component Library: Animation Toggle
Contributor(s):
Michael "Spell" Spellacy
Dependencies: Sass
*/
.animation-toggle {
display: grid;
// Control Container (Mandatory)
&__controls {
display: grid;
gap: calc(12rem / 16);
grid-auto-flow: column;
justify-self: end;
margin-block-start: calc(12rem / 16);
margin-inline-end: calc(12rem / 16);
z-index: 2;
}
// Toggle Buttons (Mandatory)
// Important: Be sure to adjust colors as needed so that button stands out on darker or lighter backgrounds.
&__pause, &__audio {
background-color: #000;
background-position: center;
background-repeat: no-repeat;
border-radius: calc(3em / 16);
border: calc(1rem / 16) #fff solid;
cursor: pointer;
height: calc(30rem / 16);
opacity: 0.5;
outline-offset: 0.25em;
transition: all 300ms ease-in-out;
width: calc(30rem / 16);
.animation-toggle:focus-within &, .animation-toggle:hover & {
opacity: 1;
}
}
&__pause {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAMAAAAp4XiDAAAAclBMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////9eWEHEAAAAJXRSTlMA8mB05+Tr4NqonE0f7si8sJWTdXNlXEk6MSTd0qKKf1UnFxEL+BREzAAAAKRJREFUSMftlscOg0AMBR/E9E5CTS/7/78YpA1klbUiwdlzfKM52wAedZjRjE/5DQZdQf5XHuMUE736JcJCZckESB1lUc3FVdkMqBVDqouXz7gIIZe4Okk4F8Dh5oNOXM45fLJfn+wkkUQSSSSRRJLt53XLEQ+5ufn7KsTcPOrkmTGuxJ1ZY3y4MLIHOmsssHC2ZIuJMQrImyE6NTBoczKkVw7AG83JGMcGlBNJAAAAAElFTkSuQmCC");
background-size: 0.625rem;
&[aria-pressed="true"] {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOoAAAE2AQMAAABV0ZztAAAABlBMVEUAAAD///+l2Z/dAAAAAXRSTlMAQObYZgAAAipJREFUaN7t2rFNBjEMhuFDFJSM8I/CaHg0j+IRXLqw/CEQBc29KSyoSPtId7kUucT+rutZccF4qInfJAOW6OlPkuqenyV6+Ysk3fOrRHN7fLIzJ322VMxzy++frAMbcwDT1L9UxTzMOrAxB3MyN/Mw68DOHMzJ3MzDrAM7czAXczMPs4zZmYO5mJt5mGXMzpzMxdzMOrAxO3MyF/Mw68DGHMzJXMzDrAMbczAnczMPsw7szMGczM08zDqwMwdzMTfzMMuYnTmYi7mZdWBjduZkLuZm1oGN2ZmTuZiHWQc25mBO5mIeZh3YmIM5mZt5mHVgZw7mZG7mYZYxO3MwF3MzD7OM2ZmDuZibWQc2ZmdO5mJuZh3YmJ05mYt5mHVgYw7mYm7mYZYxO3N8881I5mLuFQ+zdmy/yf7Pf77m14aHuVdczMkczM5szKutp5iT2Zlts2E3czEHs2/+gsNczMkczMa8OrcUczA7syHP6qyYzMFszKsDeDEnszMb8qwuPckczMa8uioWczI7s23u381czMHsm6rFMBdzMjuzMa/qTMUczM68qs41czIHszGvCqbFHMzObMizKlInczAb86q0X8zJ7My26Zc0czIHs2+6TMNczMnszIY8q75gMQezM6+6qcWczMFszKsGd1FiQQpmZ76Qh7mZkzkoBiIZ84Xcd/z4uaR3nMyO4RkZ8nAyp5kTYz8KDA3JmDmR1JxnSuCH5MAvGtDrSXGPH9boP57gj/lMAAAAAElFTkSuQmCC")
}
&[disabled] {
background-color: #333;
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAMAAAAp4XiDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDkuMC1jMDAwIDc5LjE3MWMyN2ZhYiwgMjAyMi8wOC8xNi0yMjozNTo0MSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIDI0LjAgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjFGNDQzOTFBMDNFNzExRUZBREE3RDY3NEY5MTNEMDlEIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjFGNDQzOTFCMDNFNzExRUZBREE3RDY3NEY5MTNEMDlEIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6MUY0NDM5MTgwM0U3MTFFRkFEQTdENjc0RjkxM0QwOUQiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6MUY0NDM5MTkwM0U3MTFFRkFEQTdENjc0RjkxM0QwOUQiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7n3NecAAAABlBMVEX///////9VfPVsAAAAAnRSTlP/AOW3MEoAAADVSURBVHjarNbbDsMgDAPQ5P9/epo2qQEnthetj4WzUciFSHjieJpxOr9VIQGgcMSJwgKHCVMUE654TPSCvMxmcN71SvjRXYMhBQwHXxagL1FBVad8iAgpMA3h5k2emd4eFHKteP6bg1AzLcYxEEBWFFxxOhpNpBmCwyOzISfupA6UHfWa/p5NUnyk2J35YLTRSTLHkhNTkDGmGfKFFhYkwtRETrPqtRUmvVS0q8RZLX8xWJPTLeOLZrFpSVP1Z41v0143TXxzVeDmj9eezeXKusK9BBgAWdwE4I4jwMQAAAAASUVORK5CYII=");
cursor: default;
}
[data-pause-remove] & {
display: none;
}
}
&__audio {
background-image: url("data:image/png;base64,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");
background-size: calc(18rem / 16);
}
// Video (Optional)
&__video {
width: 100%;
}
// Backdrop (Optional)
&__backdrop {
&:not(.animation-enabled &) {
&::after {
background-color: rgba(0, 0, 0, 0.5);
content: "";
pointer-events: none;
position: relative;
z-index: 1;
}
}
}
// Primary Content (Optional)
&__content {
color: #fff;
font-size: clamp(1rem, 9vw, 6rem);
font-weight: bold;
line-height: normal;
place-self: center;
text-align: center;
z-index: 2;
}
// Audio Description Track (Optional)
&__track {
align-self: end;
background-color: rgba(0,0,0,.8);
border-radius: 0.3125em;
color: #fff;
display: none;
font-size: 0.9rem;
line-height: normal;
margin: 1em;
padding: 0.7em;
text-align: center;
z-index: 1;
@media(min-width: 42.5em) {
font-size: 1.1rem;
justify-self: center;
}
&.active {
display: block;
&:empty {
display: none;
}
}
}
// Shared
&__controls, &__video, &__content, &__track, &__backdrop::after {
grid-area: 1 / 1;
}
}
// Example: Callback (After Effects)
.callback-example {
grid-area: 1 / 1;
justify-self: center;
max-width: calc(800rem/16);
width: 100%;
}/*!
Radancy Component Library: Animation Toggle
Contributor(s):
Michael "Spell" Spellacy
Dependencies: None
*/
(() => {
"use strict";
const loadLanguagePack = (url, callback) => {
// Install Language Pack.
const getComponentLanguagePack = document.getElementById("component-library-language-pack");
if (!getComponentLanguagePack) {
const componentLanguagePack = document.createElement("script");
componentLanguagePack.setAttribute("src", url);
componentLanguagePack.setAttribute("id", "component-library-language-pack");
componentLanguagePack.addEventListener("load", callback);
document.head.appendChild(componentLanguagePack);
} else {
getComponentLanguagePack.addEventListener("load", callback);
}
};
const initAnimationToggle = () => {
loadLanguagePack("https://services.tmpwebeng.com/component-library/language-pack.js", () => {
// Display which component in use via console:
console.log("%cAnimation Toggle%cv2.9.0", "background: #2d2d2d; color: #fff; padding: 6px 10px; border-radius: 16px 0 0 16px; font-weight: 600;" , "background: #6e00ee; color: #fff; padding: 6px 10px; border-radius: 0 16px 16px 0; font-weight: 600;");
// Animation variables
const animationBody = document.body;
const atAudioDescriptionClassName = "animation-toggle__audio";
const atClass = ".animation-toggle";
const atCallback = document.querySelectorAll(`${atClass}[data-callback]`);
const atCookieName = "AnimationPaused";
const atDescriptionTrackClass = ".animation-toggle__track";
const atEnabledClassName = "animation-enabled";
const atPauseButtonClassName = "animation-toggle__pause";
const atPauseButtonClass = `.${atPauseButtonClassName}`;
const atVideoClass = ".animation-toggle__video";
const atVideoControlsName = "animation-toggle__controls";
const dataAudioDescriptionButton = "data-audio-description-button";
const dataAutoplayDisable = "data-autoplay-disable";
const dataDecorative = "data-decorative";
const dataLoop = "data-loop";
const dataPauseButton = "data-pause-button";
const dataPoster = "data-poster";
const getAnimationWrappers = document.querySelectorAll(atClass);
const getBackgroundVideos = document.querySelectorAll(atVideoClass);
// Language
const atAudioDescriptionLabel = window.atAudioDescriptionLabel;
const atPauseButtonLabel = window.atPauseButtonLabel;
const atVideoLabel = window.atVideoLabel;
// Used to retrieve cookie and pause video(s) if present.
const getCookie = name => {
const match = document.cookie.match(RegExp(`(?:^|;\\s*)${name}=([^;]*)`));
return match ? match[1] : null;
};
// Assign cookie to variable on page load.
let animationPaused = getCookie(atCookieName);
// Used to set and remove cookie.
const setCookie = state => {
document.cookie = `${atCookieName}=${state}; SameSite=None; Secure; path=/`;
};
// If animation disabled in OS settings and cookie is not present then force pause.
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
if(animationPaused === null) {
setCookie("true");
animationPaused = getCookie(atCookieName);
}
}
// For each animation wrapper...
getAnimationWrappers.forEach(wrapper => {
// Create control wrapper.
const btnControls = document.createElement("div");
btnControls.setAttribute("class", atVideoControlsName);
// Prepend control wrapper to atClass.
wrapper.prepend(btnControls);
// Create pause button
const btnPlayPause = document.createElement("button");
// See if wrapper contains custom pause button value; use over default if true.
btnPlayPause.setAttribute("aria-label", wrapper.hasAttribute(dataPauseButton) ? wrapper.getAttribute(dataPauseButton) : atPauseButtonLabel);
// Add class to pause button.
btnPlayPause.classList.add(atPauseButtonClassName);
// Check to see if cookie is false or null.
if(animationPaused === "false" || animationPaused === null) {
btnPlayPause.setAttribute("aria-pressed", "false");
animationBody.classList.add(atEnabledClassName);
} else {
btnPlayPause.setAttribute("aria-pressed", "true");
animationBody.classList.remove(atEnabledClassName);
}
// Append pause button.
btnControls.append(btnPlayPause);
// Pause Toggle Event.
btnPlayPause.addEventListener("click", function() {
const getAtPauseButtonClass = document.querySelectorAll(atPauseButtonClass);
const animationPauseToggles = getAtPauseButtonClass;
if (this.getAttribute("aria-pressed") === "false") {
animationBody.classList.remove(atEnabledClassName);
setCookie("true");
animationPauseToggles.forEach(button => {
button.setAttribute("aria-pressed", "true");
});
} else {
animationBody.classList.add(atEnabledClassName);
setCookie("false");
animationPauseToggles.forEach(button => {
button.setAttribute("aria-pressed", "false");
});
}
getBackgroundVideos.forEach(video => {
if(!video.closest(atClass).querySelector(atPauseButtonClass).hasAttribute("disabled")) {
const isPaused = video.closest(atClass).querySelector(atPauseButtonClass).getAttribute("aria-pressed");
if(isPaused === "false") {
video.play();
} else {
video.pause();
}
}
});
if(this.closest(atClass).hasAttribute("data-callback")) {
const thisButton = this;
const callBackFunction = this.closest(atClass).getAttribute("data-callback");
customCallback(thisButton, callBackFunction);
}
});
if (wrapper.querySelector("track") !== null) {
const btnAudioDescription = document.createElement("button");
btnAudioDescription.setAttribute("aria-label", wrapper.hasAttribute(dataAudioDescriptionButton) ? wrapper.getAttribute(dataAudioDescriptionButton) : atAudioDescriptionLabel);
btnAudioDescription.setAttribute("aria-pressed", "false");
btnAudioDescription.setAttribute("class", atAudioDescriptionClassName);
btnControls.append(btnAudioDescription);
btnAudioDescription.addEventListener("click", function () {
const thisVideo = wrapper.querySelector(atVideoClass);
const thisDescription = wrapper.querySelector(atDescriptionTrackClass);
thisDescription.classList.toggle("active");
const isPressed = this.getAttribute("aria-pressed") === "true";
const track = thisVideo.textTracks ? thisVideo.textTracks[0] : null;
if (!isPressed) {
this.setAttribute("aria-pressed", "true");
if (track) {
track.mode = "hidden";
track.oncuechange = function () {
const currentCue = this.activeCues[0];
if (currentCue) {
thisDescription.innerText = "";
thisDescription.appendChild(currentCue.getCueAsHTML());
const Message = thisDescription.textContent;
const msg = new SpeechSynthesisUtterance(Message);
window.speechSynthesis.speak(msg);
}
};
}
} else {
this.setAttribute("aria-pressed", "false");
if (track) {
track.oncuechange = null;
}
thisDescription.innerText = "";
window.speechSynthesis.cancel();
}
});
}
});
getBackgroundVideos.forEach((video, e) => {
video.setAttribute("crossorigin", "anonymous")
video.setAttribute("disableRemotePlayback", "");
video.setAttribute("disablePictureInPicture", "");
video.id = `animation-toggle-video-${e + 1}`;
video.setAttribute("playsinline" , "");
video.muted = true;
video.load();
if (animationBody.classList.contains(atEnabledClassName)) {
if(!video.hasAttribute(dataAutoplayDisable)) {
video.onloadeddata = () => {
const playPromise = video.play();
if (playPromise !== undefined) {
playPromise.then(() => {
// Automatic playback started!
}).catch(error => {
console.error('Playback error:', error);
});
}
}
}
} else {
if(video.hasAttribute(dataPoster)) {
video.currentTime = video.getAttribute(dataPoster);
}
}
if(video.hasAttribute(dataLoop)) {
let iterations = 1;
video.addEventListener("ended", () => {
if (iterations < video.getAttribute(dataLoop)) {
video.currentTime = 0;
video.play();
iterations ++;
} else {
const toggleButton = video.closest(atClass).querySelector(atPauseButtonClass);
if(toggleButton) {
toggleButton.setAttribute("disabled", "");
}
}
}, false);
} else {
video.setAttribute("loop" , "");
}
if(!video.hasAttribute("aria-label")) {
video.setAttribute("aria-label", atVideoLabel);
}
if(video.hasAttribute(dataDecorative)) {
video.setAttribute("aria-hidden", "true");
video.setAttribute("tabindex", "-1");
video.removeAttribute("aria-label");
}
});
const customCallback = (thisButton, customCallBackName) => {
if (customCallBackName !== null) {
window[customCallBackName](thisButton);
}
};
atCallback.forEach(callback => {
const thisButton = callback.querySelector(atPauseButtonClass);
const callBackFunction = callback.dataset.callback;
customCallback(thisButton, callBackFunction);
});
});
};
initAnimationToggle();
})();// Example: Callback (After Effects)
// Check if Lottie library is present
if (typeof lottie !== "undefined") {
var callBackTarget = document.querySelector(".callback-example");
if (callBackTarget) {
var callBackAnimation = callBackTarget.getAttribute("data-animation-path");
var animation = lottie.loadAnimation({
container: callBackTarget,
loop: true,
autoplay: false,
path: callBackAnimation,
});
}
} else {
console.log("%c Lottie library is not loaded. Please include the library before using it. ", "background: #FF0000; color: #fff");
}
function toggleAfterEffects(thisButton) {
if (typeof lottie !== "undefined") {
var animationEnabled = document.body.classList.contains("animation-enabled");
if (animationEnabled) {
animation.play();
// console.log("Total frames:", animation.totalFrames);
} else {
setTimeout(function(){
// Optional: This if statement is only required if animation is needed to show specific frame on page load. If not needed, animation.pause() will suffice.
// TODO: The timeout is needed to give the script a moment for class to be added before the if statement fires. Without it, the animation fires goToAndStop
if(thisButton.classList.contains("animation-pause")) {
animation.pause();
} else {
var defaultFrame = Number(callBackTarget.getAttribute("data-animation-pause"));
animation.goToAndStop(defaultFrame, true);
}
}, 10);
}
}
// Optional: Only use if you do not need animation to show specific frame on page load.
thisButton.addEventListener("click", function() {
thisButton.classList.add("animation-pause");
});
}
<div class="animation-toggle">
<img alt="" class="css-example" height="300" src="https://radancy.dev/component-library/animation-toggle/demo.png" width="300">
</div>/*!
Radancy Component Library: Animation Toggle
Contributor(s):
Michael "Spell" Spellacy
Dependencies: Sass
*/
.animation-toggle {
display: grid;
// Control Container (Mandatory)
&__controls {
display: grid;
gap: calc(12rem / 16);
grid-auto-flow: column;
justify-self: end;
margin-block-start: calc(12rem / 16);
margin-inline-end: calc(12rem / 16);
z-index: 2;
}
// Toggle Buttons (Mandatory)
// Important: Be sure to adjust colors as needed so that button stands out on darker or lighter backgrounds.
&__pause, &__audio {
background-color: #000;
background-position: center;
background-repeat: no-repeat;
border-radius: calc(3em / 16);
border: calc(1rem / 16) #fff solid;
cursor: pointer;
height: calc(30rem / 16);
opacity: 0.5;
outline-offset: 0.25em;
transition: all 300ms ease-in-out;
width: calc(30rem / 16);
.animation-toggle:focus-within &, .animation-toggle:hover & {
opacity: 1;
}
}
&__pause {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAMAAAAp4XiDAAAAclBMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////9eWEHEAAAAJXRSTlMA8mB05+Tr4NqonE0f7si8sJWTdXNlXEk6MSTd0qKKf1UnFxEL+BREzAAAAKRJREFUSMftlscOg0AMBR/E9E5CTS/7/78YpA1klbUiwdlzfKM52wAedZjRjE/5DQZdQf5XHuMUE736JcJCZckESB1lUc3FVdkMqBVDqouXz7gIIZe4Okk4F8Dh5oNOXM45fLJfn+wkkUQSSSSRRJLt53XLEQ+5ufn7KsTcPOrkmTGuxJ1ZY3y4MLIHOmsssHC2ZIuJMQrImyE6NTBoczKkVw7AG83JGMcGlBNJAAAAAElFTkSuQmCC");
background-size: 0.625rem;
&[aria-pressed="true"] {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOoAAAE2AQMAAABV0ZztAAAABlBMVEUAAAD///+l2Z/dAAAAAXRSTlMAQObYZgAAAipJREFUaN7t2rFNBjEMhuFDFJSM8I/CaHg0j+IRXLqw/CEQBc29KSyoSPtId7kUucT+rutZccF4qInfJAOW6OlPkuqenyV6+Ysk3fOrRHN7fLIzJ322VMxzy++frAMbcwDT1L9UxTzMOrAxB3MyN/Mw68DOHMzJ3MzDrAM7czAXczMPs4zZmYO5mJt5mGXMzpzMxdzMOrAxO3MyF/Mw68DGHMzJXMzDrAMbczAnczMPsw7szMGczM08zDqwMwdzMTfzMMuYnTmYi7mZdWBjduZkLuZm1oGN2ZmTuZiHWQc25mBO5mIeZh3YmIM5mZt5mHVgZw7mZG7mYZYxO3MwF3MzD7OM2ZmDuZibWQc2ZmdO5mJuZh3YmJ05mYt5mHVgYw7mYm7mYZYxO3N8881I5mLuFQ+zdmy/yf7Pf77m14aHuVdczMkczM5szKutp5iT2Zlts2E3czEHs2/+gsNczMkczMa8OrcUczA7syHP6qyYzMFszKsDeDEnszMb8qwuPckczMa8uioWczI7s23u381czMHsm6rFMBdzMjuzMa/qTMUczM68qs41czIHszGvCqbFHMzObMizKlInczAb86q0X8zJ7My26Zc0czIHs2+6TMNczMnszIY8q75gMQezM6+6qcWczMFszKsGd1FiQQpmZ76Qh7mZkzkoBiIZ84Xcd/z4uaR3nMyO4RkZ8nAyp5kTYz8KDA3JmDmR1JxnSuCH5MAvGtDrSXGPH9boP57gj/lMAAAAAElFTkSuQmCC")
}
&[disabled] {
background-color: #333;
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAMAAAAp4XiDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDkuMC1jMDAwIDc5LjE3MWMyN2ZhYiwgMjAyMi8wOC8xNi0yMjozNTo0MSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIDI0LjAgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjFGNDQzOTFBMDNFNzExRUZBREE3RDY3NEY5MTNEMDlEIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjFGNDQzOTFCMDNFNzExRUZBREE3RDY3NEY5MTNEMDlEIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6MUY0NDM5MTgwM0U3MTFFRkFEQTdENjc0RjkxM0QwOUQiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6MUY0NDM5MTkwM0U3MTFFRkFEQTdENjc0RjkxM0QwOUQiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7n3NecAAAABlBMVEX///////9VfPVsAAAAAnRSTlP/AOW3MEoAAADVSURBVHjarNbbDsMgDAPQ5P9/epo2qQEnthetj4WzUciFSHjieJpxOr9VIQGgcMSJwgKHCVMUE654TPSCvMxmcN71SvjRXYMhBQwHXxagL1FBVad8iAgpMA3h5k2emd4eFHKteP6bg1AzLcYxEEBWFFxxOhpNpBmCwyOzISfupA6UHfWa/p5NUnyk2J35YLTRSTLHkhNTkDGmGfKFFhYkwtRETrPqtRUmvVS0q8RZLX8xWJPTLeOLZrFpSVP1Z41v0143TXxzVeDmj9eezeXKusK9BBgAWdwE4I4jwMQAAAAASUVORK5CYII=");
cursor: default;
}
[data-pause-remove] & {
display: none;
}
}
&__audio {
background-image: url("data:image/png;base64,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");
background-size: calc(18rem / 16);
}
// Video (Optional)
&__video {
width: 100%;
}
// Backdrop (Optional)
&__backdrop {
&:not(.animation-enabled &) {
&::after {
background-color: rgba(0, 0, 0, 0.5);
content: "";
pointer-events: none;
position: relative;
z-index: 1;
}
}
}
// Primary Content (Optional)
&__content {
color: #fff;
font-size: clamp(1rem, 9vw, 6rem);
font-weight: bold;
line-height: normal;
place-self: center;
text-align: center;
z-index: 2;
}
// Audio Description Track (Optional)
&__track {
align-self: end;
background-color: rgba(0,0,0,.8);
border-radius: 0.3125em;
color: #fff;
display: none;
font-size: 0.9rem;
line-height: normal;
margin: 1em;
padding: 0.7em;
text-align: center;
z-index: 1;
@media(min-width: 42.5em) {
font-size: 1.1rem;
justify-self: center;
}
&.active {
display: block;
&:empty {
display: none;
}
}
}
// Shared
&__controls, &__video, &__content, &__track, &__backdrop::after {
grid-area: 1 / 1;
}
}
// Example: CSS Animation
.css-example {
justify-self: center;
.animation-enabled & {
animation: 1s infinite both rubberBand;
}
}
@keyframes rubberBand {
0% {
transform: scale3d(1, 1, 1);
}
30% {
transform: scale3d(1.25, 0.75, 1);
}
40% {
transform: scale3d(0.75, 1.25, 1);
}
50% {
transform: scale3d(1.15, 0.85, 1);
}
65% {
transform: scale3d(.95, 1.05, 1);
}
75% {
transform: scale3d(1.05, 0.95, 1);
}
100% {
transform: scale3d(1, 1, 1);
}
}/*!
Radancy Component Library: Animation Toggle
Contributor(s):
Michael "Spell" Spellacy
Dependencies: None
*/
(() => {
"use strict";
const loadLanguagePack = (url, callback) => {
// Install Language Pack.
const getComponentLanguagePack = document.getElementById("component-library-language-pack");
if (!getComponentLanguagePack) {
const componentLanguagePack = document.createElement("script");
componentLanguagePack.setAttribute("src", url);
componentLanguagePack.setAttribute("id", "component-library-language-pack");
componentLanguagePack.addEventListener("load", callback);
document.head.appendChild(componentLanguagePack);
} else {
getComponentLanguagePack.addEventListener("load", callback);
}
};
const initAnimationToggle = () => {
loadLanguagePack("https://services.tmpwebeng.com/component-library/language-pack.js", () => {
// Display which component in use via console:
console.log("%cAnimation Toggle%cv2.9.0", "background: #2d2d2d; color: #fff; padding: 6px 10px; border-radius: 16px 0 0 16px; font-weight: 600;" , "background: #6e00ee; color: #fff; padding: 6px 10px; border-radius: 0 16px 16px 0; font-weight: 600;");
// Animation variables
const animationBody = document.body;
const atAudioDescriptionClassName = "animation-toggle__audio";
const atClass = ".animation-toggle";
const atCallback = document.querySelectorAll(`${atClass}[data-callback]`);
const atCookieName = "AnimationPaused";
const atDescriptionTrackClass = ".animation-toggle__track";
const atEnabledClassName = "animation-enabled";
const atPauseButtonClassName = "animation-toggle__pause";
const atPauseButtonClass = `.${atPauseButtonClassName}`;
const atVideoClass = ".animation-toggle__video";
const atVideoControlsName = "animation-toggle__controls";
const dataAudioDescriptionButton = "data-audio-description-button";
const dataAutoplayDisable = "data-autoplay-disable";
const dataDecorative = "data-decorative";
const dataLoop = "data-loop";
const dataPauseButton = "data-pause-button";
const dataPoster = "data-poster";
const getAnimationWrappers = document.querySelectorAll(atClass);
const getBackgroundVideos = document.querySelectorAll(atVideoClass);
// Language
const atAudioDescriptionLabel = window.atAudioDescriptionLabel;
const atPauseButtonLabel = window.atPauseButtonLabel;
const atVideoLabel = window.atVideoLabel;
// Used to retrieve cookie and pause video(s) if present.
const getCookie = name => {
const match = document.cookie.match(RegExp(`(?:^|;\\s*)${name}=([^;]*)`));
return match ? match[1] : null;
};
// Assign cookie to variable on page load.
let animationPaused = getCookie(atCookieName);
// Used to set and remove cookie.
const setCookie = state => {
document.cookie = `${atCookieName}=${state}; SameSite=None; Secure; path=/`;
};
// If animation disabled in OS settings and cookie is not present then force pause.
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
if(animationPaused === null) {
setCookie("true");
animationPaused = getCookie(atCookieName);
}
}
// For each animation wrapper...
getAnimationWrappers.forEach(wrapper => {
// Create control wrapper.
const btnControls = document.createElement("div");
btnControls.setAttribute("class", atVideoControlsName);
// Prepend control wrapper to atClass.
wrapper.prepend(btnControls);
// Create pause button
const btnPlayPause = document.createElement("button");
// See if wrapper contains custom pause button value; use over default if true.
btnPlayPause.setAttribute("aria-label", wrapper.hasAttribute(dataPauseButton) ? wrapper.getAttribute(dataPauseButton) : atPauseButtonLabel);
// Add class to pause button.
btnPlayPause.classList.add(atPauseButtonClassName);
// Check to see if cookie is false or null.
if(animationPaused === "false" || animationPaused === null) {
btnPlayPause.setAttribute("aria-pressed", "false");
animationBody.classList.add(atEnabledClassName);
} else {
btnPlayPause.setAttribute("aria-pressed", "true");
animationBody.classList.remove(atEnabledClassName);
}
// Append pause button.
btnControls.append(btnPlayPause);
// Pause Toggle Event.
btnPlayPause.addEventListener("click", function() {
const getAtPauseButtonClass = document.querySelectorAll(atPauseButtonClass);
const animationPauseToggles = getAtPauseButtonClass;
if (this.getAttribute("aria-pressed") === "false") {
animationBody.classList.remove(atEnabledClassName);
setCookie("true");
animationPauseToggles.forEach(button => {
button.setAttribute("aria-pressed", "true");
});
} else {
animationBody.classList.add(atEnabledClassName);
setCookie("false");
animationPauseToggles.forEach(button => {
button.setAttribute("aria-pressed", "false");
});
}
getBackgroundVideos.forEach(video => {
if(!video.closest(atClass).querySelector(atPauseButtonClass).hasAttribute("disabled")) {
const isPaused = video.closest(atClass).querySelector(atPauseButtonClass).getAttribute("aria-pressed");
if(isPaused === "false") {
video.play();
} else {
video.pause();
}
}
});
if(this.closest(atClass).hasAttribute("data-callback")) {
const thisButton = this;
const callBackFunction = this.closest(atClass).getAttribute("data-callback");
customCallback(thisButton, callBackFunction);
}
});
if (wrapper.querySelector("track") !== null) {
const btnAudioDescription = document.createElement("button");
btnAudioDescription.setAttribute("aria-label", wrapper.hasAttribute(dataAudioDescriptionButton) ? wrapper.getAttribute(dataAudioDescriptionButton) : atAudioDescriptionLabel);
btnAudioDescription.setAttribute("aria-pressed", "false");
btnAudioDescription.setAttribute("class", atAudioDescriptionClassName);
btnControls.append(btnAudioDescription);
btnAudioDescription.addEventListener("click", function () {
const thisVideo = wrapper.querySelector(atVideoClass);
const thisDescription = wrapper.querySelector(atDescriptionTrackClass);
thisDescription.classList.toggle("active");
const isPressed = this.getAttribute("aria-pressed") === "true";
const track = thisVideo.textTracks ? thisVideo.textTracks[0] : null;
if (!isPressed) {
this.setAttribute("aria-pressed", "true");
if (track) {
track.mode = "hidden";
track.oncuechange = function () {
const currentCue = this.activeCues[0];
if (currentCue) {
thisDescription.innerText = "";
thisDescription.appendChild(currentCue.getCueAsHTML());
const Message = thisDescription.textContent;
const msg = new SpeechSynthesisUtterance(Message);
window.speechSynthesis.speak(msg);
}
};
}
} else {
this.setAttribute("aria-pressed", "false");
if (track) {
track.oncuechange = null;
}
thisDescription.innerText = "";
window.speechSynthesis.cancel();
}
});
}
});
getBackgroundVideos.forEach((video, e) => {
video.setAttribute("crossorigin", "anonymous")
video.setAttribute("disableRemotePlayback", "");
video.setAttribute("disablePictureInPicture", "");
video.id = `animation-toggle-video-${e + 1}`;
video.setAttribute("playsinline" , "");
video.muted = true;
video.load();
if (animationBody.classList.contains(atEnabledClassName)) {
if(!video.hasAttribute(dataAutoplayDisable)) {
video.onloadeddata = () => {
const playPromise = video.play();
if (playPromise !== undefined) {
playPromise.then(() => {
// Automatic playback started!
}).catch(error => {
console.error('Playback error:', error);
});
}
}
}
} else {
if(video.hasAttribute(dataPoster)) {
video.currentTime = video.getAttribute(dataPoster);
}
}
if(video.hasAttribute(dataLoop)) {
let iterations = 1;
video.addEventListener("ended", () => {
if (iterations < video.getAttribute(dataLoop)) {
video.currentTime = 0;
video.play();
iterations ++;
} else {
const toggleButton = video.closest(atClass).querySelector(atPauseButtonClass);
if(toggleButton) {
toggleButton.setAttribute("disabled", "");
}
}
}, false);
} else {
video.setAttribute("loop" , "");
}
if(!video.hasAttribute("aria-label")) {
video.setAttribute("aria-label", atVideoLabel);
}
if(video.hasAttribute(dataDecorative)) {
video.setAttribute("aria-hidden", "true");
video.setAttribute("tabindex", "-1");
video.removeAttribute("aria-label");
}
});
const customCallback = (thisButton, customCallBackName) => {
if (customCallBackName !== null) {
window[customCallBackName](thisButton);
}
};
atCallback.forEach(callback => {
const thisButton = callback.querySelector(atPauseButtonClass);
const callBackFunction = callback.dataset.callback;
customCallback(thisButton, callBackFunction);
});
});
};
initAnimationToggle();
})();Note: In this example, the video is set to loop one time. Once complete, it cannot be restarted.
<div class="animation-toggle">
<video class="animation-toggle__video" data-loop="1" loading="lazy">
<source src="https://radancy.dev/component-library/animation-toggle/demo-lg.mp4">
</video>
</div>/*!
Radancy Component Library: Animation Toggle
Contributor(s):
Michael "Spell" Spellacy
Dependencies: Sass
*/
.animation-toggle {
display: grid;
// Control Container (Mandatory)
&__controls {
display: grid;
gap: calc(12rem / 16);
grid-auto-flow: column;
justify-self: end;
margin-block-start: calc(12rem / 16);
margin-inline-end: calc(12rem / 16);
z-index: 2;
}
// Toggle Buttons (Mandatory)
// Important: Be sure to adjust colors as needed so that button stands out on darker or lighter backgrounds.
&__pause, &__audio {
background-color: #000;
background-position: center;
background-repeat: no-repeat;
border-radius: calc(3em / 16);
border: calc(1rem / 16) #fff solid;
cursor: pointer;
height: calc(30rem / 16);
opacity: 0.5;
outline-offset: 0.25em;
transition: all 300ms ease-in-out;
width: calc(30rem / 16);
.animation-toggle:focus-within &, .animation-toggle:hover & {
opacity: 1;
}
}
&__pause {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAMAAAAp4XiDAAAAclBMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////9eWEHEAAAAJXRSTlMA8mB05+Tr4NqonE0f7si8sJWTdXNlXEk6MSTd0qKKf1UnFxEL+BREzAAAAKRJREFUSMftlscOg0AMBR/E9E5CTS/7/78YpA1klbUiwdlzfKM52wAedZjRjE/5DQZdQf5XHuMUE736JcJCZckESB1lUc3FVdkMqBVDqouXz7gIIZe4Okk4F8Dh5oNOXM45fLJfn+wkkUQSSSSRRJLt53XLEQ+5ufn7KsTcPOrkmTGuxJ1ZY3y4MLIHOmsssHC2ZIuJMQrImyE6NTBoczKkVw7AG83JGMcGlBNJAAAAAElFTkSuQmCC");
background-size: 0.625rem;
&[aria-pressed="true"] {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOoAAAE2AQMAAABV0ZztAAAABlBMVEUAAAD///+l2Z/dAAAAAXRSTlMAQObYZgAAAipJREFUaN7t2rFNBjEMhuFDFJSM8I/CaHg0j+IRXLqw/CEQBc29KSyoSPtId7kUucT+rutZccF4qInfJAOW6OlPkuqenyV6+Ysk3fOrRHN7fLIzJ322VMxzy++frAMbcwDT1L9UxTzMOrAxB3MyN/Mw68DOHMzJ3MzDrAM7czAXczMPs4zZmYO5mJt5mGXMzpzMxdzMOrAxO3MyF/Mw68DGHMzJXMzDrAMbczAnczMPsw7szMGczM08zDqwMwdzMTfzMMuYnTmYi7mZdWBjduZkLuZm1oGN2ZmTuZiHWQc25mBO5mIeZh3YmIM5mZt5mHVgZw7mZG7mYZYxO3MwF3MzD7OM2ZmDuZibWQc2ZmdO5mJuZh3YmJ05mYt5mHVgYw7mYm7mYZYxO3N8881I5mLuFQ+zdmy/yf7Pf77m14aHuVdczMkczM5szKutp5iT2Zlts2E3czEHs2/+gsNczMkczMa8OrcUczA7syHP6qyYzMFszKsDeDEnszMb8qwuPckczMa8uioWczI7s23u381czMHsm6rFMBdzMjuzMa/qTMUczM68qs41czIHszGvCqbFHMzObMizKlInczAb86q0X8zJ7My26Zc0czIHs2+6TMNczMnszIY8q75gMQezM6+6qcWczMFszKsGd1FiQQpmZ76Qh7mZkzkoBiIZ84Xcd/z4uaR3nMyO4RkZ8nAyp5kTYz8KDA3JmDmR1JxnSuCH5MAvGtDrSXGPH9boP57gj/lMAAAAAElFTkSuQmCC")
}
&[disabled] {
background-color: #333;
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAMAAAAp4XiDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDkuMC1jMDAwIDc5LjE3MWMyN2ZhYiwgMjAyMi8wOC8xNi0yMjozNTo0MSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIDI0LjAgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjFGNDQzOTFBMDNFNzExRUZBREE3RDY3NEY5MTNEMDlEIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjFGNDQzOTFCMDNFNzExRUZBREE3RDY3NEY5MTNEMDlEIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6MUY0NDM5MTgwM0U3MTFFRkFEQTdENjc0RjkxM0QwOUQiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6MUY0NDM5MTkwM0U3MTFFRkFEQTdENjc0RjkxM0QwOUQiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7n3NecAAAABlBMVEX///////9VfPVsAAAAAnRSTlP/AOW3MEoAAADVSURBVHjarNbbDsMgDAPQ5P9/epo2qQEnthetj4WzUciFSHjieJpxOr9VIQGgcMSJwgKHCVMUE654TPSCvMxmcN71SvjRXYMhBQwHXxagL1FBVad8iAgpMA3h5k2emd4eFHKteP6bg1AzLcYxEEBWFFxxOhpNpBmCwyOzISfupA6UHfWa/p5NUnyk2J35YLTRSTLHkhNTkDGmGfKFFhYkwtRETrPqtRUmvVS0q8RZLX8xWJPTLeOLZrFpSVP1Z41v0143TXxzVeDmj9eezeXKusK9BBgAWdwE4I4jwMQAAAAASUVORK5CYII=");
cursor: default;
}
[data-pause-remove] & {
display: none;
}
}
&__audio {
background-image: url("data:image/png;base64,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");
background-size: calc(18rem / 16);
}
// Video (Optional)
&__video {
width: 100%;
}
// Backdrop (Optional)
&__backdrop {
&:not(.animation-enabled &) {
&::after {
background-color: rgba(0, 0, 0, 0.5);
content: "";
pointer-events: none;
position: relative;
z-index: 1;
}
}
}
// Primary Content (Optional)
&__content {
color: #fff;
font-size: clamp(1rem, 9vw, 6rem);
font-weight: bold;
line-height: normal;
place-self: center;
text-align: center;
z-index: 2;
}
// Audio Description Track (Optional)
&__track {
align-self: end;
background-color: rgba(0,0,0,.8);
border-radius: 0.3125em;
color: #fff;
display: none;
font-size: 0.9rem;
line-height: normal;
margin: 1em;
padding: 0.7em;
text-align: center;
z-index: 1;
@media(min-width: 42.5em) {
font-size: 1.1rem;
justify-self: center;
}
&.active {
display: block;
&:empty {
display: none;
}
}
}
// Shared
&__controls, &__video, &__content, &__track, &__backdrop::after {
grid-area: 1 / 1;
}
}
/*!
Radancy Component Library: Animation Toggle
Contributor(s):
Michael "Spell" Spellacy
Dependencies: None
*/
(() => {
"use strict";
const loadLanguagePack = (url, callback) => {
// Install Language Pack.
const getComponentLanguagePack = document.getElementById("component-library-language-pack");
if (!getComponentLanguagePack) {
const componentLanguagePack = document.createElement("script");
componentLanguagePack.setAttribute("src", url);
componentLanguagePack.setAttribute("id", "component-library-language-pack");
componentLanguagePack.addEventListener("load", callback);
document.head.appendChild(componentLanguagePack);
} else {
getComponentLanguagePack.addEventListener("load", callback);
}
};
const initAnimationToggle = () => {
loadLanguagePack("https://services.tmpwebeng.com/component-library/language-pack.js", () => {
// Display which component in use via console:
console.log("%cAnimation Toggle%cv2.9.0", "background: #2d2d2d; color: #fff; padding: 6px 10px; border-radius: 16px 0 0 16px; font-weight: 600;" , "background: #6e00ee; color: #fff; padding: 6px 10px; border-radius: 0 16px 16px 0; font-weight: 600;");
// Animation variables
const animationBody = document.body;
const atAudioDescriptionClassName = "animation-toggle__audio";
const atClass = ".animation-toggle";
const atCallback = document.querySelectorAll(`${atClass}[data-callback]`);
const atCookieName = "AnimationPaused";
const atDescriptionTrackClass = ".animation-toggle__track";
const atEnabledClassName = "animation-enabled";
const atPauseButtonClassName = "animation-toggle__pause";
const atPauseButtonClass = `.${atPauseButtonClassName}`;
const atVideoClass = ".animation-toggle__video";
const atVideoControlsName = "animation-toggle__controls";
const dataAudioDescriptionButton = "data-audio-description-button";
const dataAutoplayDisable = "data-autoplay-disable";
const dataDecorative = "data-decorative";
const dataLoop = "data-loop";
const dataPauseButton = "data-pause-button";
const dataPoster = "data-poster";
const getAnimationWrappers = document.querySelectorAll(atClass);
const getBackgroundVideos = document.querySelectorAll(atVideoClass);
// Language
const atAudioDescriptionLabel = window.atAudioDescriptionLabel;
const atPauseButtonLabel = window.atPauseButtonLabel;
const atVideoLabel = window.atVideoLabel;
// Used to retrieve cookie and pause video(s) if present.
const getCookie = name => {
const match = document.cookie.match(RegExp(`(?:^|;\\s*)${name}=([^;]*)`));
return match ? match[1] : null;
};
// Assign cookie to variable on page load.
let animationPaused = getCookie(atCookieName);
// Used to set and remove cookie.
const setCookie = state => {
document.cookie = `${atCookieName}=${state}; SameSite=None; Secure; path=/`;
};
// If animation disabled in OS settings and cookie is not present then force pause.
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
if(animationPaused === null) {
setCookie("true");
animationPaused = getCookie(atCookieName);
}
}
// For each animation wrapper...
getAnimationWrappers.forEach(wrapper => {
// Create control wrapper.
const btnControls = document.createElement("div");
btnControls.setAttribute("class", atVideoControlsName);
// Prepend control wrapper to atClass.
wrapper.prepend(btnControls);
// Create pause button
const btnPlayPause = document.createElement("button");
// See if wrapper contains custom pause button value; use over default if true.
btnPlayPause.setAttribute("aria-label", wrapper.hasAttribute(dataPauseButton) ? wrapper.getAttribute(dataPauseButton) : atPauseButtonLabel);
// Add class to pause button.
btnPlayPause.classList.add(atPauseButtonClassName);
// Check to see if cookie is false or null.
if(animationPaused === "false" || animationPaused === null) {
btnPlayPause.setAttribute("aria-pressed", "false");
animationBody.classList.add(atEnabledClassName);
} else {
btnPlayPause.setAttribute("aria-pressed", "true");
animationBody.classList.remove(atEnabledClassName);
}
// Append pause button.
btnControls.append(btnPlayPause);
// Pause Toggle Event.
btnPlayPause.addEventListener("click", function() {
const getAtPauseButtonClass = document.querySelectorAll(atPauseButtonClass);
const animationPauseToggles = getAtPauseButtonClass;
if (this.getAttribute("aria-pressed") === "false") {
animationBody.classList.remove(atEnabledClassName);
setCookie("true");
animationPauseToggles.forEach(button => {
button.setAttribute("aria-pressed", "true");
});
} else {
animationBody.classList.add(atEnabledClassName);
setCookie("false");
animationPauseToggles.forEach(button => {
button.setAttribute("aria-pressed", "false");
});
}
getBackgroundVideos.forEach(video => {
if(!video.closest(atClass).querySelector(atPauseButtonClass).hasAttribute("disabled")) {
const isPaused = video.closest(atClass).querySelector(atPauseButtonClass).getAttribute("aria-pressed");
if(isPaused === "false") {
video.play();
} else {
video.pause();
}
}
});
if(this.closest(atClass).hasAttribute("data-callback")) {
const thisButton = this;
const callBackFunction = this.closest(atClass).getAttribute("data-callback");
customCallback(thisButton, callBackFunction);
}
});
if (wrapper.querySelector("track") !== null) {
const btnAudioDescription = document.createElement("button");
btnAudioDescription.setAttribute("aria-label", wrapper.hasAttribute(dataAudioDescriptionButton) ? wrapper.getAttribute(dataAudioDescriptionButton) : atAudioDescriptionLabel);
btnAudioDescription.setAttribute("aria-pressed", "false");
btnAudioDescription.setAttribute("class", atAudioDescriptionClassName);
btnControls.append(btnAudioDescription);
btnAudioDescription.addEventListener("click", function () {
const thisVideo = wrapper.querySelector(atVideoClass);
const thisDescription = wrapper.querySelector(atDescriptionTrackClass);
thisDescription.classList.toggle("active");
const isPressed = this.getAttribute("aria-pressed") === "true";
const track = thisVideo.textTracks ? thisVideo.textTracks[0] : null;
if (!isPressed) {
this.setAttribute("aria-pressed", "true");
if (track) {
track.mode = "hidden";
track.oncuechange = function () {
const currentCue = this.activeCues[0];
if (currentCue) {
thisDescription.innerText = "";
thisDescription.appendChild(currentCue.getCueAsHTML());
const Message = thisDescription.textContent;
const msg = new SpeechSynthesisUtterance(Message);
window.speechSynthesis.speak(msg);
}
};
}
} else {
this.setAttribute("aria-pressed", "false");
if (track) {
track.oncuechange = null;
}
thisDescription.innerText = "";
window.speechSynthesis.cancel();
}
});
}
});
getBackgroundVideos.forEach((video, e) => {
video.setAttribute("crossorigin", "anonymous")
video.setAttribute("disableRemotePlayback", "");
video.setAttribute("disablePictureInPicture", "");
video.id = `animation-toggle-video-${e + 1}`;
video.setAttribute("playsinline" , "");
video.muted = true;
video.load();
if (animationBody.classList.contains(atEnabledClassName)) {
if(!video.hasAttribute(dataAutoplayDisable)) {
video.onloadeddata = () => {
const playPromise = video.play();
if (playPromise !== undefined) {
playPromise.then(() => {
// Automatic playback started!
}).catch(error => {
console.error('Playback error:', error);
});
}
}
}
} else {
if(video.hasAttribute(dataPoster)) {
video.currentTime = video.getAttribute(dataPoster);
}
}
if(video.hasAttribute(dataLoop)) {
let iterations = 1;
video.addEventListener("ended", () => {
if (iterations < video.getAttribute(dataLoop)) {
video.currentTime = 0;
video.play();
iterations ++;
} else {
const toggleButton = video.closest(atClass).querySelector(atPauseButtonClass);
if(toggleButton) {
toggleButton.setAttribute("disabled", "");
}
}
}, false);
} else {
video.setAttribute("loop" , "");
}
if(!video.hasAttribute("aria-label")) {
video.setAttribute("aria-label", atVideoLabel);
}
if(video.hasAttribute(dataDecorative)) {
video.setAttribute("aria-hidden", "true");
video.setAttribute("tabindex", "-1");
video.removeAttribute("aria-label");
}
});
const customCallback = (thisButton, customCallBackName) => {
if (customCallBackName !== null) {
window[customCallBackName](thisButton);
}
};
atCallback.forEach(callback => {
const thisButton = callback.querySelector(atPauseButtonClass);
const callBackFunction = callback.dataset.callback;
customCallback(thisButton, callBackFunction);
});
});
};
initAnimationToggle();
})();Experimental: This is an experimental feature that will allow the video to include an audio description. A valid, third-party or self-produced VTT file is required.
<div class="animation-toggle">
<video class="animation-toggle__video">
<source src="https://radancy.dev/component-library/animation-toggle/demo-lg.mp4">
<track kind="descriptions" label="English" src="https://radancy.dev/component-library/animation-toggle/demo.vtt" srclang="en-US">
</video>
<div aria-live="polite" class="animation-toggle__track"></div>
</div>/*!
Radancy Component Library: Animation Toggle
Contributor(s):
Michael "Spell" Spellacy
Dependencies: Sass
*/
.animation-toggle {
display: grid;
// Control Container (Mandatory)
&__controls {
display: grid;
gap: calc(12rem / 16);
grid-auto-flow: column;
justify-self: end;
margin-block-start: calc(12rem / 16);
margin-inline-end: calc(12rem / 16);
z-index: 2;
}
// Toggle Buttons (Mandatory)
// Important: Be sure to adjust colors as needed so that button stands out on darker or lighter backgrounds.
&__pause, &__audio {
background-color: #000;
background-position: center;
background-repeat: no-repeat;
border-radius: calc(3em / 16);
border: calc(1rem / 16) #fff solid;
cursor: pointer;
height: calc(30rem / 16);
opacity: 0.5;
outline-offset: 0.25em;
transition: all 300ms ease-in-out;
width: calc(30rem / 16);
.animation-toggle:focus-within &, .animation-toggle:hover & {
opacity: 1;
}
}
&__pause {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAMAAAAp4XiDAAAAclBMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////9eWEHEAAAAJXRSTlMA8mB05+Tr4NqonE0f7si8sJWTdXNlXEk6MSTd0qKKf1UnFxEL+BREzAAAAKRJREFUSMftlscOg0AMBR/E9E5CTS/7/78YpA1klbUiwdlzfKM52wAedZjRjE/5DQZdQf5XHuMUE736JcJCZckESB1lUc3FVdkMqBVDqouXz7gIIZe4Okk4F8Dh5oNOXM45fLJfn+wkkUQSSSSRRJLt53XLEQ+5ufn7KsTcPOrkmTGuxJ1ZY3y4MLIHOmsssHC2ZIuJMQrImyE6NTBoczKkVw7AG83JGMcGlBNJAAAAAElFTkSuQmCC");
background-size: 0.625rem;
&[aria-pressed="true"] {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOoAAAE2AQMAAABV0ZztAAAABlBMVEUAAAD///+l2Z/dAAAAAXRSTlMAQObYZgAAAipJREFUaN7t2rFNBjEMhuFDFJSM8I/CaHg0j+IRXLqw/CEQBc29KSyoSPtId7kUucT+rutZccF4qInfJAOW6OlPkuqenyV6+Ysk3fOrRHN7fLIzJ322VMxzy++frAMbcwDT1L9UxTzMOrAxB3MyN/Mw68DOHMzJ3MzDrAM7czAXczMPs4zZmYO5mJt5mGXMzpzMxdzMOrAxO3MyF/Mw68DGHMzJXMzDrAMbczAnczMPsw7szMGczM08zDqwMwdzMTfzMMuYnTmYi7mZdWBjduZkLuZm1oGN2ZmTuZiHWQc25mBO5mIeZh3YmIM5mZt5mHVgZw7mZG7mYZYxO3MwF3MzD7OM2ZmDuZibWQc2ZmdO5mJuZh3YmJ05mYt5mHVgYw7mYm7mYZYxO3N8881I5mLuFQ+zdmy/yf7Pf77m14aHuVdczMkczM5szKutp5iT2Zlts2E3czEHs2/+gsNczMkczMa8OrcUczA7syHP6qyYzMFszKsDeDEnszMb8qwuPckczMa8uioWczI7s23u381czMHsm6rFMBdzMjuzMa/qTMUczM68qs41czIHszGvCqbFHMzObMizKlInczAb86q0X8zJ7My26Zc0czIHs2+6TMNczMnszIY8q75gMQezM6+6qcWczMFszKsGd1FiQQpmZ76Qh7mZkzkoBiIZ84Xcd/z4uaR3nMyO4RkZ8nAyp5kTYz8KDA3JmDmR1JxnSuCH5MAvGtDrSXGPH9boP57gj/lMAAAAAElFTkSuQmCC")
}
&[disabled] {
background-color: #333;
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAMAAAAp4XiDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDkuMC1jMDAwIDc5LjE3MWMyN2ZhYiwgMjAyMi8wOC8xNi0yMjozNTo0MSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIDI0LjAgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjFGNDQzOTFBMDNFNzExRUZBREE3RDY3NEY5MTNEMDlEIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjFGNDQzOTFCMDNFNzExRUZBREE3RDY3NEY5MTNEMDlEIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6MUY0NDM5MTgwM0U3MTFFRkFEQTdENjc0RjkxM0QwOUQiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6MUY0NDM5MTkwM0U3MTFFRkFEQTdENjc0RjkxM0QwOUQiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7n3NecAAAABlBMVEX///////9VfPVsAAAAAnRSTlP/AOW3MEoAAADVSURBVHjarNbbDsMgDAPQ5P9/epo2qQEnthetj4WzUciFSHjieJpxOr9VIQGgcMSJwgKHCVMUE654TPSCvMxmcN71SvjRXYMhBQwHXxagL1FBVad8iAgpMA3h5k2emd4eFHKteP6bg1AzLcYxEEBWFFxxOhpNpBmCwyOzISfupA6UHfWa/p5NUnyk2J35YLTRSTLHkhNTkDGmGfKFFhYkwtRETrPqtRUmvVS0q8RZLX8xWJPTLeOLZrFpSVP1Z41v0143TXxzVeDmj9eezeXKusK9BBgAWdwE4I4jwMQAAAAASUVORK5CYII=");
cursor: default;
}
[data-pause-remove] & {
display: none;
}
}
&__audio {
background-image: url("data:image/png;base64,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");
background-size: calc(18rem / 16);
}
// Video (Optional)
&__video {
width: 100%;
}
// Backdrop (Optional)
&__backdrop {
&:not(.animation-enabled &) {
&::after {
background-color: rgba(0, 0, 0, 0.5);
content: "";
pointer-events: none;
position: relative;
z-index: 1;
}
}
}
// Primary Content (Optional)
&__content {
color: #fff;
font-size: clamp(1rem, 9vw, 6rem);
font-weight: bold;
line-height: normal;
place-self: center;
text-align: center;
z-index: 2;
}
// Audio Description Track (Optional)
&__track {
align-self: end;
background-color: rgba(0,0,0,.8);
border-radius: 0.3125em;
color: #fff;
display: none;
font-size: 0.9rem;
line-height: normal;
margin: 1em;
padding: 0.7em;
text-align: center;
z-index: 1;
@media(min-width: 42.5em) {
font-size: 1.1rem;
justify-self: center;
}
&.active {
display: block;
&:empty {
display: none;
}
}
}
// Shared
&__controls, &__video, &__content, &__track, &__backdrop::after {
grid-area: 1 / 1;
}
}
/*!
Radancy Component Library: Animation Toggle
Contributor(s):
Michael "Spell" Spellacy
Dependencies: None
*/
(() => {
"use strict";
const loadLanguagePack = (url, callback) => {
// Install Language Pack.
const getComponentLanguagePack = document.getElementById("component-library-language-pack");
if (!getComponentLanguagePack) {
const componentLanguagePack = document.createElement("script");
componentLanguagePack.setAttribute("src", url);
componentLanguagePack.setAttribute("id", "component-library-language-pack");
componentLanguagePack.addEventListener("load", callback);
document.head.appendChild(componentLanguagePack);
} else {
getComponentLanguagePack.addEventListener("load", callback);
}
};
const initAnimationToggle = () => {
loadLanguagePack("https://services.tmpwebeng.com/component-library/language-pack.js", () => {
// Display which component in use via console:
console.log("%cAnimation Toggle%cv2.9.0", "background: #2d2d2d; color: #fff; padding: 6px 10px; border-radius: 16px 0 0 16px; font-weight: 600;" , "background: #6e00ee; color: #fff; padding: 6px 10px; border-radius: 0 16px 16px 0; font-weight: 600;");
// Animation variables
const animationBody = document.body;
const atAudioDescriptionClassName = "animation-toggle__audio";
const atClass = ".animation-toggle";
const atCallback = document.querySelectorAll(`${atClass}[data-callback]`);
const atCookieName = "AnimationPaused";
const atDescriptionTrackClass = ".animation-toggle__track";
const atEnabledClassName = "animation-enabled";
const atPauseButtonClassName = "animation-toggle__pause";
const atPauseButtonClass = `.${atPauseButtonClassName}`;
const atVideoClass = ".animation-toggle__video";
const atVideoControlsName = "animation-toggle__controls";
const dataAudioDescriptionButton = "data-audio-description-button";
const dataAutoplayDisable = "data-autoplay-disable";
const dataDecorative = "data-decorative";
const dataLoop = "data-loop";
const dataPauseButton = "data-pause-button";
const dataPoster = "data-poster";
const getAnimationWrappers = document.querySelectorAll(atClass);
const getBackgroundVideos = document.querySelectorAll(atVideoClass);
// Language
const atAudioDescriptionLabel = window.atAudioDescriptionLabel;
const atPauseButtonLabel = window.atPauseButtonLabel;
const atVideoLabel = window.atVideoLabel;
// Used to retrieve cookie and pause video(s) if present.
const getCookie = name => {
const match = document.cookie.match(RegExp(`(?:^|;\\s*)${name}=([^;]*)`));
return match ? match[1] : null;
};
// Assign cookie to variable on page load.
let animationPaused = getCookie(atCookieName);
// Used to set and remove cookie.
const setCookie = state => {
document.cookie = `${atCookieName}=${state}; SameSite=None; Secure; path=/`;
};
// If animation disabled in OS settings and cookie is not present then force pause.
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
if(animationPaused === null) {
setCookie("true");
animationPaused = getCookie(atCookieName);
}
}
// For each animation wrapper...
getAnimationWrappers.forEach(wrapper => {
// Create control wrapper.
const btnControls = document.createElement("div");
btnControls.setAttribute("class", atVideoControlsName);
// Prepend control wrapper to atClass.
wrapper.prepend(btnControls);
// Create pause button
const btnPlayPause = document.createElement("button");
// See if wrapper contains custom pause button value; use over default if true.
btnPlayPause.setAttribute("aria-label", wrapper.hasAttribute(dataPauseButton) ? wrapper.getAttribute(dataPauseButton) : atPauseButtonLabel);
// Add class to pause button.
btnPlayPause.classList.add(atPauseButtonClassName);
// Check to see if cookie is false or null.
if(animationPaused === "false" || animationPaused === null) {
btnPlayPause.setAttribute("aria-pressed", "false");
animationBody.classList.add(atEnabledClassName);
} else {
btnPlayPause.setAttribute("aria-pressed", "true");
animationBody.classList.remove(atEnabledClassName);
}
// Append pause button.
btnControls.append(btnPlayPause);
// Pause Toggle Event.
btnPlayPause.addEventListener("click", function() {
const getAtPauseButtonClass = document.querySelectorAll(atPauseButtonClass);
const animationPauseToggles = getAtPauseButtonClass;
if (this.getAttribute("aria-pressed") === "false") {
animationBody.classList.remove(atEnabledClassName);
setCookie("true");
animationPauseToggles.forEach(button => {
button.setAttribute("aria-pressed", "true");
});
} else {
animationBody.classList.add(atEnabledClassName);
setCookie("false");
animationPauseToggles.forEach(button => {
button.setAttribute("aria-pressed", "false");
});
}
getBackgroundVideos.forEach(video => {
if(!video.closest(atClass).querySelector(atPauseButtonClass).hasAttribute("disabled")) {
const isPaused = video.closest(atClass).querySelector(atPauseButtonClass).getAttribute("aria-pressed");
if(isPaused === "false") {
video.play();
} else {
video.pause();
}
}
});
if(this.closest(atClass).hasAttribute("data-callback")) {
const thisButton = this;
const callBackFunction = this.closest(atClass).getAttribute("data-callback");
customCallback(thisButton, callBackFunction);
}
});
if (wrapper.querySelector("track") !== null) {
const btnAudioDescription = document.createElement("button");
btnAudioDescription.setAttribute("aria-label", wrapper.hasAttribute(dataAudioDescriptionButton) ? wrapper.getAttribute(dataAudioDescriptionButton) : atAudioDescriptionLabel);
btnAudioDescription.setAttribute("aria-pressed", "false");
btnAudioDescription.setAttribute("class", atAudioDescriptionClassName);
btnControls.append(btnAudioDescription);
btnAudioDescription.addEventListener("click", function () {
const thisVideo = wrapper.querySelector(atVideoClass);
const thisDescription = wrapper.querySelector(atDescriptionTrackClass);
thisDescription.classList.toggle("active");
const isPressed = this.getAttribute("aria-pressed") === "true";
const track = thisVideo.textTracks ? thisVideo.textTracks[0] : null;
if (!isPressed) {
this.setAttribute("aria-pressed", "true");
if (track) {
track.mode = "hidden";
track.oncuechange = function () {
const currentCue = this.activeCues[0];
if (currentCue) {
thisDescription.innerText = "";
thisDescription.appendChild(currentCue.getCueAsHTML());
const Message = thisDescription.textContent;
const msg = new SpeechSynthesisUtterance(Message);
window.speechSynthesis.speak(msg);
}
};
}
} else {
this.setAttribute("aria-pressed", "false");
if (track) {
track.oncuechange = null;
}
thisDescription.innerText = "";
window.speechSynthesis.cancel();
}
});
}
});
getBackgroundVideos.forEach((video, e) => {
video.setAttribute("crossorigin", "anonymous")
video.setAttribute("disableRemotePlayback", "");
video.setAttribute("disablePictureInPicture", "");
video.id = `animation-toggle-video-${e + 1}`;
video.setAttribute("playsinline" , "");
video.muted = true;
video.load();
if (animationBody.classList.contains(atEnabledClassName)) {
if(!video.hasAttribute(dataAutoplayDisable)) {
video.onloadeddata = () => {
const playPromise = video.play();
if (playPromise !== undefined) {
playPromise.then(() => {
// Automatic playback started!
}).catch(error => {
console.error('Playback error:', error);
});
}
}
}
} else {
if(video.hasAttribute(dataPoster)) {
video.currentTime = video.getAttribute(dataPoster);
}
}
if(video.hasAttribute(dataLoop)) {
let iterations = 1;
video.addEventListener("ended", () => {
if (iterations < video.getAttribute(dataLoop)) {
video.currentTime = 0;
video.play();
iterations ++;
} else {
const toggleButton = video.closest(atClass).querySelector(atPauseButtonClass);
if(toggleButton) {
toggleButton.setAttribute("disabled", "");
}
}
}, false);
} else {
video.setAttribute("loop" , "");
}
if(!video.hasAttribute("aria-label")) {
video.setAttribute("aria-label", atVideoLabel);
}
if(video.hasAttribute(dataDecorative)) {
video.setAttribute("aria-hidden", "true");
video.setAttribute("tabindex", "-1");
video.removeAttribute("aria-label");
}
});
const customCallback = (thisButton, customCallBackName) => {
if (customCallBackName !== null) {
window[customCallBackName](thisButton);
}
};
atCallback.forEach(callback => {
const thisButton = callback.querySelector(atPauseButtonClass);
const callBackFunction = callback.dataset.callback;
customCallback(thisButton, callBackFunction);
});
});
};
initAnimationToggle();
})();
Note: To "pause" an animated GIF, you will need two images: the animated version and a static version, which can be any desired frame. Animated GIFs should only be used as a last resort when video is not a viable option.
<div class="animation-toggle">
<img alt="" class="gif-animation gif-animation--on" height="300" src="https://radancy.dev/component-library/animation-toggle/demo-animation-on.gif" width="300">
<img alt="" class="gif-animation gif-animation--off" height="300" src="https://radancy.dev/component-library/animation-toggle/demo-animation-off.gif" width="300">
</div>/*!
Radancy Component Library: Animation Toggle
Contributor(s):
Michael "Spell" Spellacy
Dependencies: Sass
*/
.animation-toggle {
display: grid;
// Control Container (Mandatory)
&__controls {
display: grid;
gap: calc(12rem / 16);
grid-auto-flow: column;
justify-self: end;
margin-block-start: calc(12rem / 16);
margin-inline-end: calc(12rem / 16);
z-index: 2;
}
// Toggle Buttons (Mandatory)
// Important: Be sure to adjust colors as needed so that button stands out on darker or lighter backgrounds.
&__pause, &__audio {
background-color: #000;
background-position: center;
background-repeat: no-repeat;
border-radius: calc(3em / 16);
border: calc(1rem / 16) #fff solid;
cursor: pointer;
height: calc(30rem / 16);
opacity: 0.5;
outline-offset: 0.25em;
transition: all 300ms ease-in-out;
width: calc(30rem / 16);
.animation-toggle:focus-within &, .animation-toggle:hover & {
opacity: 1;
}
}
&__pause {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAMAAAAp4XiDAAAAclBMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////9eWEHEAAAAJXRSTlMA8mB05+Tr4NqonE0f7si8sJWTdXNlXEk6MSTd0qKKf1UnFxEL+BREzAAAAKRJREFUSMftlscOg0AMBR/E9E5CTS/7/78YpA1klbUiwdlzfKM52wAedZjRjE/5DQZdQf5XHuMUE736JcJCZckESB1lUc3FVdkMqBVDqouXz7gIIZe4Okk4F8Dh5oNOXM45fLJfn+wkkUQSSSSRRJLt53XLEQ+5ufn7KsTcPOrkmTGuxJ1ZY3y4MLIHOmsssHC2ZIuJMQrImyE6NTBoczKkVw7AG83JGMcGlBNJAAAAAElFTkSuQmCC");
background-size: 0.625rem;
&[aria-pressed="true"] {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOoAAAE2AQMAAABV0ZztAAAABlBMVEUAAAD///+l2Z/dAAAAAXRSTlMAQObYZgAAAipJREFUaN7t2rFNBjEMhuFDFJSM8I/CaHg0j+IRXLqw/CEQBc29KSyoSPtId7kUucT+rutZccF4qInfJAOW6OlPkuqenyV6+Ysk3fOrRHN7fLIzJ322VMxzy++frAMbcwDT1L9UxTzMOrAxB3MyN/Mw68DOHMzJ3MzDrAM7czAXczMPs4zZmYO5mJt5mGXMzpzMxdzMOrAxO3MyF/Mw68DGHMzJXMzDrAMbczAnczMPsw7szMGczM08zDqwMwdzMTfzMMuYnTmYi7mZdWBjduZkLuZm1oGN2ZmTuZiHWQc25mBO5mIeZh3YmIM5mZt5mHVgZw7mZG7mYZYxO3MwF3MzD7OM2ZmDuZibWQc2ZmdO5mJuZh3YmJ05mYt5mHVgYw7mYm7mYZYxO3N8881I5mLuFQ+zdmy/yf7Pf77m14aHuVdczMkczM5szKutp5iT2Zlts2E3czEHs2/+gsNczMkczMa8OrcUczA7syHP6qyYzMFszKsDeDEnszMb8qwuPckczMa8uioWczI7s23u381czMHsm6rFMBdzMjuzMa/qTMUczM68qs41czIHszGvCqbFHMzObMizKlInczAb86q0X8zJ7My26Zc0czIHs2+6TMNczMnszIY8q75gMQezM6+6qcWczMFszKsGd1FiQQpmZ76Qh7mZkzkoBiIZ84Xcd/z4uaR3nMyO4RkZ8nAyp5kTYz8KDA3JmDmR1JxnSuCH5MAvGtDrSXGPH9boP57gj/lMAAAAAElFTkSuQmCC")
}
&[disabled] {
background-color: #333;
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAMAAAAp4XiDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDkuMC1jMDAwIDc5LjE3MWMyN2ZhYiwgMjAyMi8wOC8xNi0yMjozNTo0MSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIDI0LjAgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjFGNDQzOTFBMDNFNzExRUZBREE3RDY3NEY5MTNEMDlEIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjFGNDQzOTFCMDNFNzExRUZBREE3RDY3NEY5MTNEMDlEIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6MUY0NDM5MTgwM0U3MTFFRkFEQTdENjc0RjkxM0QwOUQiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6MUY0NDM5MTkwM0U3MTFFRkFEQTdENjc0RjkxM0QwOUQiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7n3NecAAAABlBMVEX///////9VfPVsAAAAAnRSTlP/AOW3MEoAAADVSURBVHjarNbbDsMgDAPQ5P9/epo2qQEnthetj4WzUciFSHjieJpxOr9VIQGgcMSJwgKHCVMUE654TPSCvMxmcN71SvjRXYMhBQwHXxagL1FBVad8iAgpMA3h5k2emd4eFHKteP6bg1AzLcYxEEBWFFxxOhpNpBmCwyOzISfupA6UHfWa/p5NUnyk2J35YLTRSTLHkhNTkDGmGfKFFhYkwtRETrPqtRUmvVS0q8RZLX8xWJPTLeOLZrFpSVP1Z41v0143TXxzVeDmj9eezeXKusK9BBgAWdwE4I4jwMQAAAAASUVORK5CYII=");
cursor: default;
}
[data-pause-remove] & {
display: none;
}
}
&__audio {
background-image: url("data:image/png;base64,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");
background-size: calc(18rem / 16);
}
// Video (Optional)
&__video {
width: 100%;
}
// Backdrop (Optional)
&__backdrop {
&:not(.animation-enabled &) {
&::after {
background-color: rgba(0, 0, 0, 0.5);
content: "";
pointer-events: none;
position: relative;
z-index: 1;
}
}
}
// Primary Content (Optional)
&__content {
color: #fff;
font-size: clamp(1rem, 9vw, 6rem);
font-weight: bold;
line-height: normal;
place-self: center;
text-align: center;
z-index: 2;
}
// Audio Description Track (Optional)
&__track {
align-self: end;
background-color: rgba(0,0,0,.8);
border-radius: 0.3125em;
color: #fff;
display: none;
font-size: 0.9rem;
line-height: normal;
margin: 1em;
padding: 0.7em;
text-align: center;
z-index: 1;
@media(min-width: 42.5em) {
font-size: 1.1rem;
justify-self: center;
}
&.active {
display: block;
&:empty {
display: none;
}
}
}
// Shared
&__controls, &__video, &__content, &__track, &__backdrop::after {
grid-area: 1 / 1;
}
}
// Example: GIF Animation
.gif-animation {
grid-area: 1 / 1;
justify-self: center;
&--off {
display: block;
}
&--on {
display: none;
}
.animation-enabled & {
&--off {
display: none;
}
&--on {
display: block;
}
}
} /*!
Radancy Component Library: Animation Toggle
Contributor(s):
Michael "Spell" Spellacy
Dependencies: None
*/
(() => {
"use strict";
const loadLanguagePack = (url, callback) => {
// Install Language Pack.
const getComponentLanguagePack = document.getElementById("component-library-language-pack");
if (!getComponentLanguagePack) {
const componentLanguagePack = document.createElement("script");
componentLanguagePack.setAttribute("src", url);
componentLanguagePack.setAttribute("id", "component-library-language-pack");
componentLanguagePack.addEventListener("load", callback);
document.head.appendChild(componentLanguagePack);
} else {
getComponentLanguagePack.addEventListener("load", callback);
}
};
const initAnimationToggle = () => {
loadLanguagePack("https://services.tmpwebeng.com/component-library/language-pack.js", () => {
// Display which component in use via console:
console.log("%cAnimation Toggle%cv2.9.0", "background: #2d2d2d; color: #fff; padding: 6px 10px; border-radius: 16px 0 0 16px; font-weight: 600;" , "background: #6e00ee; color: #fff; padding: 6px 10px; border-radius: 0 16px 16px 0; font-weight: 600;");
// Animation variables
const animationBody = document.body;
const atAudioDescriptionClassName = "animation-toggle__audio";
const atClass = ".animation-toggle";
const atCallback = document.querySelectorAll(`${atClass}[data-callback]`);
const atCookieName = "AnimationPaused";
const atDescriptionTrackClass = ".animation-toggle__track";
const atEnabledClassName = "animation-enabled";
const atPauseButtonClassName = "animation-toggle__pause";
const atPauseButtonClass = `.${atPauseButtonClassName}`;
const atVideoClass = ".animation-toggle__video";
const atVideoControlsName = "animation-toggle__controls";
const dataAudioDescriptionButton = "data-audio-description-button";
const dataAutoplayDisable = "data-autoplay-disable";
const dataDecorative = "data-decorative";
const dataLoop = "data-loop";
const dataPauseButton = "data-pause-button";
const dataPoster = "data-poster";
const getAnimationWrappers = document.querySelectorAll(atClass);
const getBackgroundVideos = document.querySelectorAll(atVideoClass);
// Language
const atAudioDescriptionLabel = window.atAudioDescriptionLabel;
const atPauseButtonLabel = window.atPauseButtonLabel;
const atVideoLabel = window.atVideoLabel;
// Used to retrieve cookie and pause video(s) if present.
const getCookie = name => {
const match = document.cookie.match(RegExp(`(?:^|;\\s*)${name}=([^;]*)`));
return match ? match[1] : null;
};
// Assign cookie to variable on page load.
let animationPaused = getCookie(atCookieName);
// Used to set and remove cookie.
const setCookie = state => {
document.cookie = `${atCookieName}=${state}; SameSite=None; Secure; path=/`;
};
// If animation disabled in OS settings and cookie is not present then force pause.
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
if(animationPaused === null) {
setCookie("true");
animationPaused = getCookie(atCookieName);
}
}
// For each animation wrapper...
getAnimationWrappers.forEach(wrapper => {
// Create control wrapper.
const btnControls = document.createElement("div");
btnControls.setAttribute("class", atVideoControlsName);
// Prepend control wrapper to atClass.
wrapper.prepend(btnControls);
// Create pause button
const btnPlayPause = document.createElement("button");
// See if wrapper contains custom pause button value; use over default if true.
btnPlayPause.setAttribute("aria-label", wrapper.hasAttribute(dataPauseButton) ? wrapper.getAttribute(dataPauseButton) : atPauseButtonLabel);
// Add class to pause button.
btnPlayPause.classList.add(atPauseButtonClassName);
// Check to see if cookie is false or null.
if(animationPaused === "false" || animationPaused === null) {
btnPlayPause.setAttribute("aria-pressed", "false");
animationBody.classList.add(atEnabledClassName);
} else {
btnPlayPause.setAttribute("aria-pressed", "true");
animationBody.classList.remove(atEnabledClassName);
}
// Append pause button.
btnControls.append(btnPlayPause);
// Pause Toggle Event.
btnPlayPause.addEventListener("click", function() {
const getAtPauseButtonClass = document.querySelectorAll(atPauseButtonClass);
const animationPauseToggles = getAtPauseButtonClass;
if (this.getAttribute("aria-pressed") === "false") {
animationBody.classList.remove(atEnabledClassName);
setCookie("true");
animationPauseToggles.forEach(button => {
button.setAttribute("aria-pressed", "true");
});
} else {
animationBody.classList.add(atEnabledClassName);
setCookie("false");
animationPauseToggles.forEach(button => {
button.setAttribute("aria-pressed", "false");
});
}
getBackgroundVideos.forEach(video => {
if(!video.closest(atClass).querySelector(atPauseButtonClass).hasAttribute("disabled")) {
const isPaused = video.closest(atClass).querySelector(atPauseButtonClass).getAttribute("aria-pressed");
if(isPaused === "false") {
video.play();
} else {
video.pause();
}
}
});
if(this.closest(atClass).hasAttribute("data-callback")) {
const thisButton = this;
const callBackFunction = this.closest(atClass).getAttribute("data-callback");
customCallback(thisButton, callBackFunction);
}
});
if (wrapper.querySelector("track") !== null) {
const btnAudioDescription = document.createElement("button");
btnAudioDescription.setAttribute("aria-label", wrapper.hasAttribute(dataAudioDescriptionButton) ? wrapper.getAttribute(dataAudioDescriptionButton) : atAudioDescriptionLabel);
btnAudioDescription.setAttribute("aria-pressed", "false");
btnAudioDescription.setAttribute("class", atAudioDescriptionClassName);
btnControls.append(btnAudioDescription);
btnAudioDescription.addEventListener("click", function () {
const thisVideo = wrapper.querySelector(atVideoClass);
const thisDescription = wrapper.querySelector(atDescriptionTrackClass);
thisDescription.classList.toggle("active");
const isPressed = this.getAttribute("aria-pressed") === "true";
const track = thisVideo.textTracks ? thisVideo.textTracks[0] : null;
if (!isPressed) {
this.setAttribute("aria-pressed", "true");
if (track) {
track.mode = "hidden";
track.oncuechange = function () {
const currentCue = this.activeCues[0];
if (currentCue) {
thisDescription.innerText = "";
thisDescription.appendChild(currentCue.getCueAsHTML());
const Message = thisDescription.textContent;
const msg = new SpeechSynthesisUtterance(Message);
window.speechSynthesis.speak(msg);
}
};
}
} else {
this.setAttribute("aria-pressed", "false");
if (track) {
track.oncuechange = null;
}
thisDescription.innerText = "";
window.speechSynthesis.cancel();
}
});
}
});
getBackgroundVideos.forEach((video, e) => {
video.setAttribute("crossorigin", "anonymous")
video.setAttribute("disableRemotePlayback", "");
video.setAttribute("disablePictureInPicture", "");
video.id = `animation-toggle-video-${e + 1}`;
video.setAttribute("playsinline" , "");
video.muted = true;
video.load();
if (animationBody.classList.contains(atEnabledClassName)) {
if(!video.hasAttribute(dataAutoplayDisable)) {
video.onloadeddata = () => {
const playPromise = video.play();
if (playPromise !== undefined) {
playPromise.then(() => {
// Automatic playback started!
}).catch(error => {
console.error('Playback error:', error);
});
}
}
}
} else {
if(video.hasAttribute(dataPoster)) {
video.currentTime = video.getAttribute(dataPoster);
}
}
if(video.hasAttribute(dataLoop)) {
let iterations = 1;
video.addEventListener("ended", () => {
if (iterations < video.getAttribute(dataLoop)) {
video.currentTime = 0;
video.play();
iterations ++;
} else {
const toggleButton = video.closest(atClass).querySelector(atPauseButtonClass);
if(toggleButton) {
toggleButton.setAttribute("disabled", "");
}
}
}, false);
} else {
video.setAttribute("loop" , "");
}
if(!video.hasAttribute("aria-label")) {
video.setAttribute("aria-label", atVideoLabel);
}
if(video.hasAttribute(dataDecorative)) {
video.setAttribute("aria-hidden", "true");
video.setAttribute("tabindex", "-1");
video.removeAttribute("aria-label");
}
});
const customCallback = (thisButton, customCallBackName) => {
if (customCallBackName !== null) {
window[customCallBackName](thisButton);
}
};
atCallback.forEach(callback => {
const thisButton = callback.querySelector(atPauseButtonClass);
const callBackFunction = callback.dataset.callback;
customCallback(thisButton, callBackFunction);
});
});
};
initAnimationToggle();
})();<div class="animation-toggle animation-toggle__backdrop">
<video class="animation-toggle__video" loading="lazy">
<source src="https://radancy.dev/component-library/animation-toggle/demo-lg.mp4">
</video>
<div class="animation-toggle__content">Hero Banner</div>
</div>/*!
Radancy Component Library: Animation Toggle
Contributor(s):
Michael "Spell" Spellacy
Dependencies: Sass
*/
.animation-toggle {
display: grid;
// Control Container (Mandatory)
&__controls {
display: grid;
gap: calc(12rem / 16);
grid-auto-flow: column;
justify-self: end;
margin-block-start: calc(12rem / 16);
margin-inline-end: calc(12rem / 16);
z-index: 2;
}
// Toggle Buttons (Mandatory)
// Important: Be sure to adjust colors as needed so that button stands out on darker or lighter backgrounds.
&__pause, &__audio {
background-color: #000;
background-position: center;
background-repeat: no-repeat;
border-radius: calc(3em / 16);
border: calc(1rem / 16) #fff solid;
cursor: pointer;
height: calc(30rem / 16);
opacity: 0.5;
outline-offset: 0.25em;
transition: all 300ms ease-in-out;
width: calc(30rem / 16);
.animation-toggle:focus-within &, .animation-toggle:hover & {
opacity: 1;
}
}
&__pause {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAMAAAAp4XiDAAAAclBMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////9eWEHEAAAAJXRSTlMA8mB05+Tr4NqonE0f7si8sJWTdXNlXEk6MSTd0qKKf1UnFxEL+BREzAAAAKRJREFUSMftlscOg0AMBR/E9E5CTS/7/78YpA1klbUiwdlzfKM52wAedZjRjE/5DQZdQf5XHuMUE736JcJCZckESB1lUc3FVdkMqBVDqouXz7gIIZe4Okk4F8Dh5oNOXM45fLJfn+wkkUQSSSSRRJLt53XLEQ+5ufn7KsTcPOrkmTGuxJ1ZY3y4MLIHOmsssHC2ZIuJMQrImyE6NTBoczKkVw7AG83JGMcGlBNJAAAAAElFTkSuQmCC");
background-size: 0.625rem;
&[aria-pressed="true"] {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOoAAAE2AQMAAABV0ZztAAAABlBMVEUAAAD///+l2Z/dAAAAAXRSTlMAQObYZgAAAipJREFUaN7t2rFNBjEMhuFDFJSM8I/CaHg0j+IRXLqw/CEQBc29KSyoSPtId7kUucT+rutZccF4qInfJAOW6OlPkuqenyV6+Ysk3fOrRHN7fLIzJ322VMxzy++frAMbcwDT1L9UxTzMOrAxB3MyN/Mw68DOHMzJ3MzDrAM7czAXczMPs4zZmYO5mJt5mGXMzpzMxdzMOrAxO3MyF/Mw68DGHMzJXMzDrAMbczAnczMPsw7szMGczM08zDqwMwdzMTfzMMuYnTmYi7mZdWBjduZkLuZm1oGN2ZmTuZiHWQc25mBO5mIeZh3YmIM5mZt5mHVgZw7mZG7mYZYxO3MwF3MzD7OM2ZmDuZibWQc2ZmdO5mJuZh3YmJ05mYt5mHVgYw7mYm7mYZYxO3N8881I5mLuFQ+zdmy/yf7Pf77m14aHuVdczMkczM5szKutp5iT2Zlts2E3czEHs2/+gsNczMkczMa8OrcUczA7syHP6qyYzMFszKsDeDEnszMb8qwuPckczMa8uioWczI7s23u381czMHsm6rFMBdzMjuzMa/qTMUczM68qs41czIHszGvCqbFHMzObMizKlInczAb86q0X8zJ7My26Zc0czIHs2+6TMNczMnszIY8q75gMQezM6+6qcWczMFszKsGd1FiQQpmZ76Qh7mZkzkoBiIZ84Xcd/z4uaR3nMyO4RkZ8nAyp5kTYz8KDA3JmDmR1JxnSuCH5MAvGtDrSXGPH9boP57gj/lMAAAAAElFTkSuQmCC")
}
&[disabled] {
background-color: #333;
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAMAAAAp4XiDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDkuMC1jMDAwIDc5LjE3MWMyN2ZhYiwgMjAyMi8wOC8xNi0yMjozNTo0MSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIDI0LjAgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjFGNDQzOTFBMDNFNzExRUZBREE3RDY3NEY5MTNEMDlEIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjFGNDQzOTFCMDNFNzExRUZBREE3RDY3NEY5MTNEMDlEIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6MUY0NDM5MTgwM0U3MTFFRkFEQTdENjc0RjkxM0QwOUQiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6MUY0NDM5MTkwM0U3MTFFRkFEQTdENjc0RjkxM0QwOUQiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7n3NecAAAABlBMVEX///////9VfPVsAAAAAnRSTlP/AOW3MEoAAADVSURBVHjarNbbDsMgDAPQ5P9/epo2qQEnthetj4WzUciFSHjieJpxOr9VIQGgcMSJwgKHCVMUE654TPSCvMxmcN71SvjRXYMhBQwHXxagL1FBVad8iAgpMA3h5k2emd4eFHKteP6bg1AzLcYxEEBWFFxxOhpNpBmCwyOzISfupA6UHfWa/p5NUnyk2J35YLTRSTLHkhNTkDGmGfKFFhYkwtRETrPqtRUmvVS0q8RZLX8xWJPTLeOLZrFpSVP1Z41v0143TXxzVeDmj9eezeXKusK9BBgAWdwE4I4jwMQAAAAASUVORK5CYII=");
cursor: default;
}
[data-pause-remove] & {
display: none;
}
}
&__audio {
background-image: url("data:image/png;base64,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");
background-size: calc(18rem / 16);
}
// Video (Optional)
&__video {
width: 100%;
}
// Backdrop (Optional)
&__backdrop {
&:not(.animation-enabled &) {
&::after {
background-color: rgba(0, 0, 0, 0.5);
content: "";
pointer-events: none;
position: relative;
z-index: 1;
}
}
}
// Primary Content (Optional)
&__content {
color: #fff;
font-size: clamp(1rem, 9vw, 6rem);
font-weight: bold;
line-height: normal;
place-self: center;
text-align: center;
z-index: 2;
}
// Audio Description Track (Optional)
&__track {
align-self: end;
background-color: rgba(0,0,0,.8);
border-radius: 0.3125em;
color: #fff;
display: none;
font-size: 0.9rem;
line-height: normal;
margin: 1em;
padding: 0.7em;
text-align: center;
z-index: 1;
@media(min-width: 42.5em) {
font-size: 1.1rem;
justify-self: center;
}
&.active {
display: block;
&:empty {
display: none;
}
}
}
// Shared
&__controls, &__video, &__content, &__track, &__backdrop::after {
grid-area: 1 / 1;
}
}
/*!
Radancy Component Library: Animation Toggle
Contributor(s):
Michael "Spell" Spellacy
Dependencies: None
*/
(() => {
"use strict";
const loadLanguagePack = (url, callback) => {
// Install Language Pack.
const getComponentLanguagePack = document.getElementById("component-library-language-pack");
if (!getComponentLanguagePack) {
const componentLanguagePack = document.createElement("script");
componentLanguagePack.setAttribute("src", url);
componentLanguagePack.setAttribute("id", "component-library-language-pack");
componentLanguagePack.addEventListener("load", callback);
document.head.appendChild(componentLanguagePack);
} else {
getComponentLanguagePack.addEventListener("load", callback);
}
};
const initAnimationToggle = () => {
loadLanguagePack("https://services.tmpwebeng.com/component-library/language-pack.js", () => {
// Display which component in use via console:
console.log("%cAnimation Toggle%cv2.9.0", "background: #2d2d2d; color: #fff; padding: 6px 10px; border-radius: 16px 0 0 16px; font-weight: 600;" , "background: #6e00ee; color: #fff; padding: 6px 10px; border-radius: 0 16px 16px 0; font-weight: 600;");
// Animation variables
const animationBody = document.body;
const atAudioDescriptionClassName = "animation-toggle__audio";
const atClass = ".animation-toggle";
const atCallback = document.querySelectorAll(`${atClass}[data-callback]`);
const atCookieName = "AnimationPaused";
const atDescriptionTrackClass = ".animation-toggle__track";
const atEnabledClassName = "animation-enabled";
const atPauseButtonClassName = "animation-toggle__pause";
const atPauseButtonClass = `.${atPauseButtonClassName}`;
const atVideoClass = ".animation-toggle__video";
const atVideoControlsName = "animation-toggle__controls";
const dataAudioDescriptionButton = "data-audio-description-button";
const dataAutoplayDisable = "data-autoplay-disable";
const dataDecorative = "data-decorative";
const dataLoop = "data-loop";
const dataPauseButton = "data-pause-button";
const dataPoster = "data-poster";
const getAnimationWrappers = document.querySelectorAll(atClass);
const getBackgroundVideos = document.querySelectorAll(atVideoClass);
// Language
const atAudioDescriptionLabel = window.atAudioDescriptionLabel;
const atPauseButtonLabel = window.atPauseButtonLabel;
const atVideoLabel = window.atVideoLabel;
// Used to retrieve cookie and pause video(s) if present.
const getCookie = name => {
const match = document.cookie.match(RegExp(`(?:^|;\\s*)${name}=([^;]*)`));
return match ? match[1] : null;
};
// Assign cookie to variable on page load.
let animationPaused = getCookie(atCookieName);
// Used to set and remove cookie.
const setCookie = state => {
document.cookie = `${atCookieName}=${state}; SameSite=None; Secure; path=/`;
};
// If animation disabled in OS settings and cookie is not present then force pause.
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
if(animationPaused === null) {
setCookie("true");
animationPaused = getCookie(atCookieName);
}
}
// For each animation wrapper...
getAnimationWrappers.forEach(wrapper => {
// Create control wrapper.
const btnControls = document.createElement("div");
btnControls.setAttribute("class", atVideoControlsName);
// Prepend control wrapper to atClass.
wrapper.prepend(btnControls);
// Create pause button
const btnPlayPause = document.createElement("button");
// See if wrapper contains custom pause button value; use over default if true.
btnPlayPause.setAttribute("aria-label", wrapper.hasAttribute(dataPauseButton) ? wrapper.getAttribute(dataPauseButton) : atPauseButtonLabel);
// Add class to pause button.
btnPlayPause.classList.add(atPauseButtonClassName);
// Check to see if cookie is false or null.
if(animationPaused === "false" || animationPaused === null) {
btnPlayPause.setAttribute("aria-pressed", "false");
animationBody.classList.add(atEnabledClassName);
} else {
btnPlayPause.setAttribute("aria-pressed", "true");
animationBody.classList.remove(atEnabledClassName);
}
// Append pause button.
btnControls.append(btnPlayPause);
// Pause Toggle Event.
btnPlayPause.addEventListener("click", function() {
const getAtPauseButtonClass = document.querySelectorAll(atPauseButtonClass);
const animationPauseToggles = getAtPauseButtonClass;
if (this.getAttribute("aria-pressed") === "false") {
animationBody.classList.remove(atEnabledClassName);
setCookie("true");
animationPauseToggles.forEach(button => {
button.setAttribute("aria-pressed", "true");
});
} else {
animationBody.classList.add(atEnabledClassName);
setCookie("false");
animationPauseToggles.forEach(button => {
button.setAttribute("aria-pressed", "false");
});
}
getBackgroundVideos.forEach(video => {
if(!video.closest(atClass).querySelector(atPauseButtonClass).hasAttribute("disabled")) {
const isPaused = video.closest(atClass).querySelector(atPauseButtonClass).getAttribute("aria-pressed");
if(isPaused === "false") {
video.play();
} else {
video.pause();
}
}
});
if(this.closest(atClass).hasAttribute("data-callback")) {
const thisButton = this;
const callBackFunction = this.closest(atClass).getAttribute("data-callback");
customCallback(thisButton, callBackFunction);
}
});
if (wrapper.querySelector("track") !== null) {
const btnAudioDescription = document.createElement("button");
btnAudioDescription.setAttribute("aria-label", wrapper.hasAttribute(dataAudioDescriptionButton) ? wrapper.getAttribute(dataAudioDescriptionButton) : atAudioDescriptionLabel);
btnAudioDescription.setAttribute("aria-pressed", "false");
btnAudioDescription.setAttribute("class", atAudioDescriptionClassName);
btnControls.append(btnAudioDescription);
btnAudioDescription.addEventListener("click", function () {
const thisVideo = wrapper.querySelector(atVideoClass);
const thisDescription = wrapper.querySelector(atDescriptionTrackClass);
thisDescription.classList.toggle("active");
const isPressed = this.getAttribute("aria-pressed") === "true";
const track = thisVideo.textTracks ? thisVideo.textTracks[0] : null;
if (!isPressed) {
this.setAttribute("aria-pressed", "true");
if (track) {
track.mode = "hidden";
track.oncuechange = function () {
const currentCue = this.activeCues[0];
if (currentCue) {
thisDescription.innerText = "";
thisDescription.appendChild(currentCue.getCueAsHTML());
const Message = thisDescription.textContent;
const msg = new SpeechSynthesisUtterance(Message);
window.speechSynthesis.speak(msg);
}
};
}
} else {
this.setAttribute("aria-pressed", "false");
if (track) {
track.oncuechange = null;
}
thisDescription.innerText = "";
window.speechSynthesis.cancel();
}
});
}
});
getBackgroundVideos.forEach((video, e) => {
video.setAttribute("crossorigin", "anonymous")
video.setAttribute("disableRemotePlayback", "");
video.setAttribute("disablePictureInPicture", "");
video.id = `animation-toggle-video-${e + 1}`;
video.setAttribute("playsinline" , "");
video.muted = true;
video.load();
if (animationBody.classList.contains(atEnabledClassName)) {
if(!video.hasAttribute(dataAutoplayDisable)) {
video.onloadeddata = () => {
const playPromise = video.play();
if (playPromise !== undefined) {
playPromise.then(() => {
// Automatic playback started!
}).catch(error => {
console.error('Playback error:', error);
});
}
}
}
} else {
if(video.hasAttribute(dataPoster)) {
video.currentTime = video.getAttribute(dataPoster);
}
}
if(video.hasAttribute(dataLoop)) {
let iterations = 1;
video.addEventListener("ended", () => {
if (iterations < video.getAttribute(dataLoop)) {
video.currentTime = 0;
video.play();
iterations ++;
} else {
const toggleButton = video.closest(atClass).querySelector(atPauseButtonClass);
if(toggleButton) {
toggleButton.setAttribute("disabled", "");
}
}
}, false);
} else {
video.setAttribute("loop" , "");
}
if(!video.hasAttribute("aria-label")) {
video.setAttribute("aria-label", atVideoLabel);
}
if(video.hasAttribute(dataDecorative)) {
video.setAttribute("aria-hidden", "true");
video.setAttribute("tabindex", "-1");
video.removeAttribute("aria-label");
}
});
const customCallback = (thisButton, customCallBackName) => {
if (customCallBackName !== null) {
window[customCallBackName](thisButton);
}
};
atCallback.forEach(callback => {
const thisButton = callback.querySelector(atPauseButtonClass);
const callBackFunction = callback.dataset.callback;
customCallback(thisButton, callBackFunction);
});
});
};
initAnimationToggle();
})();<div class="animation-toggle">
<div class="animation-grid">
<video class="animation-toggle__video" loading="lazy">
<source src="https://radancy.dev/component-library/animation-toggle/demo-sm.mp4">
</video>
<video class="animation-toggle__video" loading="lazy">
<source src="https://radancy.dev/component-library/animation-toggle/demo-sm.mp4#t=5">
</video>
<video class="animation-toggle__video" loading="lazy">
<source src="https://radancy.dev/component-library/animation-toggle/demo-sm.mp4#t=10">
</video>
<video class="animation-toggle__video" loading="lazy">
<source src="https://radancy.dev/component-library/animation-toggle/demo-sm.mp4#t=15">
</video>
</div>
</div>/*!
Radancy Component Library: Animation Toggle
Contributor(s):
Michael "Spell" Spellacy
Dependencies: Sass
*/
.animation-toggle {
display: grid;
// Control Container (Mandatory)
&__controls {
display: grid;
gap: calc(12rem / 16);
grid-auto-flow: column;
justify-self: end;
margin-block-start: calc(12rem / 16);
margin-inline-end: calc(12rem / 16);
z-index: 2;
}
// Toggle Buttons (Mandatory)
// Important: Be sure to adjust colors as needed so that button stands out on darker or lighter backgrounds.
&__pause, &__audio {
background-color: #000;
background-position: center;
background-repeat: no-repeat;
border-radius: calc(3em / 16);
border: calc(1rem / 16) #fff solid;
cursor: pointer;
height: calc(30rem / 16);
opacity: 0.5;
outline-offset: 0.25em;
transition: all 300ms ease-in-out;
width: calc(30rem / 16);
.animation-toggle:focus-within &, .animation-toggle:hover & {
opacity: 1;
}
}
&__pause {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAMAAAAp4XiDAAAAclBMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////9eWEHEAAAAJXRSTlMA8mB05+Tr4NqonE0f7si8sJWTdXNlXEk6MSTd0qKKf1UnFxEL+BREzAAAAKRJREFUSMftlscOg0AMBR/E9E5CTS/7/78YpA1klbUiwdlzfKM52wAedZjRjE/5DQZdQf5XHuMUE736JcJCZckESB1lUc3FVdkMqBVDqouXz7gIIZe4Okk4F8Dh5oNOXM45fLJfn+wkkUQSSSSRRJLt53XLEQ+5ufn7KsTcPOrkmTGuxJ1ZY3y4MLIHOmsssHC2ZIuJMQrImyE6NTBoczKkVw7AG83JGMcGlBNJAAAAAElFTkSuQmCC");
background-size: 0.625rem;
&[aria-pressed="true"] {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOoAAAE2AQMAAABV0ZztAAAABlBMVEUAAAD///+l2Z/dAAAAAXRSTlMAQObYZgAAAipJREFUaN7t2rFNBjEMhuFDFJSM8I/CaHg0j+IRXLqw/CEQBc29KSyoSPtId7kUucT+rutZccF4qInfJAOW6OlPkuqenyV6+Ysk3fOrRHN7fLIzJ322VMxzy++frAMbcwDT1L9UxTzMOrAxB3MyN/Mw68DOHMzJ3MzDrAM7czAXczMPs4zZmYO5mJt5mGXMzpzMxdzMOrAxO3MyF/Mw68DGHMzJXMzDrAMbczAnczMPsw7szMGczM08zDqwMwdzMTfzMMuYnTmYi7mZdWBjduZkLuZm1oGN2ZmTuZiHWQc25mBO5mIeZh3YmIM5mZt5mHVgZw7mZG7mYZYxO3MwF3MzD7OM2ZmDuZibWQc2ZmdO5mJuZh3YmJ05mYt5mHVgYw7mYm7mYZYxO3N8881I5mLuFQ+zdmy/yf7Pf77m14aHuVdczMkczM5szKutp5iT2Zlts2E3czEHs2/+gsNczMkczMa8OrcUczA7syHP6qyYzMFszKsDeDEnszMb8qwuPckczMa8uioWczI7s23u381czMHsm6rFMBdzMjuzMa/qTMUczM68qs41czIHszGvCqbFHMzObMizKlInczAb86q0X8zJ7My26Zc0czIHs2+6TMNczMnszIY8q75gMQezM6+6qcWczMFszKsGd1FiQQpmZ76Qh7mZkzkoBiIZ84Xcd/z4uaR3nMyO4RkZ8nAyp5kTYz8KDA3JmDmR1JxnSuCH5MAvGtDrSXGPH9boP57gj/lMAAAAAElFTkSuQmCC")
}
&[disabled] {
background-color: #333;
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAMAAAAp4XiDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDkuMC1jMDAwIDc5LjE3MWMyN2ZhYiwgMjAyMi8wOC8xNi0yMjozNTo0MSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIDI0LjAgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjFGNDQzOTFBMDNFNzExRUZBREE3RDY3NEY5MTNEMDlEIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjFGNDQzOTFCMDNFNzExRUZBREE3RDY3NEY5MTNEMDlEIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6MUY0NDM5MTgwM0U3MTFFRkFEQTdENjc0RjkxM0QwOUQiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6MUY0NDM5MTkwM0U3MTFFRkFEQTdENjc0RjkxM0QwOUQiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7n3NecAAAABlBMVEX///////9VfPVsAAAAAnRSTlP/AOW3MEoAAADVSURBVHjarNbbDsMgDAPQ5P9/epo2qQEnthetj4WzUciFSHjieJpxOr9VIQGgcMSJwgKHCVMUE654TPSCvMxmcN71SvjRXYMhBQwHXxagL1FBVad8iAgpMA3h5k2emd4eFHKteP6bg1AzLcYxEEBWFFxxOhpNpBmCwyOzISfupA6UHfWa/p5NUnyk2J35YLTRSTLHkhNTkDGmGfKFFhYkwtRETrPqtRUmvVS0q8RZLX8xWJPTLeOLZrFpSVP1Z41v0143TXxzVeDmj9eezeXKusK9BBgAWdwE4I4jwMQAAAAASUVORK5CYII=");
cursor: default;
}
[data-pause-remove] & {
display: none;
}
}
&__audio {
background-image: url("data:image/png;base64,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");
background-size: calc(18rem / 16);
}
// Video (Optional)
&__video {
width: 100%;
}
// Backdrop (Optional)
&__backdrop {
&:not(.animation-enabled &) {
&::after {
background-color: rgba(0, 0, 0, 0.5);
content: "";
pointer-events: none;
position: relative;
z-index: 1;
}
}
}
// Primary Content (Optional)
&__content {
color: #fff;
font-size: clamp(1rem, 9vw, 6rem);
font-weight: bold;
line-height: normal;
place-self: center;
text-align: center;
z-index: 2;
}
// Audio Description Track (Optional)
&__track {
align-self: end;
background-color: rgba(0,0,0,.8);
border-radius: 0.3125em;
color: #fff;
display: none;
font-size: 0.9rem;
line-height: normal;
margin: 1em;
padding: 0.7em;
text-align: center;
z-index: 1;
@media(min-width: 42.5em) {
font-size: 1.1rem;
justify-self: center;
}
&.active {
display: block;
&:empty {
display: none;
}
}
}
// Shared
&__controls, &__video, &__content, &__track, &__backdrop::after {
grid-area: 1 / 1;
}
}
// Example: Multiple Videos
.animation-grid {
display: grid;
gap: 1em;
grid-area: 1 / 1;
grid-template-columns: 1fr 1fr;
video {
grid-area: unset;
}
}/*!
Radancy Component Library: Animation Toggle
Contributor(s):
Michael "Spell" Spellacy
Dependencies: None
*/
(() => {
"use strict";
const loadLanguagePack = (url, callback) => {
// Install Language Pack.
const getComponentLanguagePack = document.getElementById("component-library-language-pack");
if (!getComponentLanguagePack) {
const componentLanguagePack = document.createElement("script");
componentLanguagePack.setAttribute("src", url);
componentLanguagePack.setAttribute("id", "component-library-language-pack");
componentLanguagePack.addEventListener("load", callback);
document.head.appendChild(componentLanguagePack);
} else {
getComponentLanguagePack.addEventListener("load", callback);
}
};
const initAnimationToggle = () => {
loadLanguagePack("https://services.tmpwebeng.com/component-library/language-pack.js", () => {
// Display which component in use via console:
console.log("%cAnimation Toggle%cv2.9.0", "background: #2d2d2d; color: #fff; padding: 6px 10px; border-radius: 16px 0 0 16px; font-weight: 600;" , "background: #6e00ee; color: #fff; padding: 6px 10px; border-radius: 0 16px 16px 0; font-weight: 600;");
// Animation variables
const animationBody = document.body;
const atAudioDescriptionClassName = "animation-toggle__audio";
const atClass = ".animation-toggle";
const atCallback = document.querySelectorAll(`${atClass}[data-callback]`);
const atCookieName = "AnimationPaused";
const atDescriptionTrackClass = ".animation-toggle__track";
const atEnabledClassName = "animation-enabled";
const atPauseButtonClassName = "animation-toggle__pause";
const atPauseButtonClass = `.${atPauseButtonClassName}`;
const atVideoClass = ".animation-toggle__video";
const atVideoControlsName = "animation-toggle__controls";
const dataAudioDescriptionButton = "data-audio-description-button";
const dataAutoplayDisable = "data-autoplay-disable";
const dataDecorative = "data-decorative";
const dataLoop = "data-loop";
const dataPauseButton = "data-pause-button";
const dataPoster = "data-poster";
const getAnimationWrappers = document.querySelectorAll(atClass);
const getBackgroundVideos = document.querySelectorAll(atVideoClass);
// Language
const atAudioDescriptionLabel = window.atAudioDescriptionLabel;
const atPauseButtonLabel = window.atPauseButtonLabel;
const atVideoLabel = window.atVideoLabel;
// Used to retrieve cookie and pause video(s) if present.
const getCookie = name => {
const match = document.cookie.match(RegExp(`(?:^|;\\s*)${name}=([^;]*)`));
return match ? match[1] : null;
};
// Assign cookie to variable on page load.
let animationPaused = getCookie(atCookieName);
// Used to set and remove cookie.
const setCookie = state => {
document.cookie = `${atCookieName}=${state}; SameSite=None; Secure; path=/`;
};
// If animation disabled in OS settings and cookie is not present then force pause.
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
if(animationPaused === null) {
setCookie("true");
animationPaused = getCookie(atCookieName);
}
}
// For each animation wrapper...
getAnimationWrappers.forEach(wrapper => {
// Create control wrapper.
const btnControls = document.createElement("div");
btnControls.setAttribute("class", atVideoControlsName);
// Prepend control wrapper to atClass.
wrapper.prepend(btnControls);
// Create pause button
const btnPlayPause = document.createElement("button");
// See if wrapper contains custom pause button value; use over default if true.
btnPlayPause.setAttribute("aria-label", wrapper.hasAttribute(dataPauseButton) ? wrapper.getAttribute(dataPauseButton) : atPauseButtonLabel);
// Add class to pause button.
btnPlayPause.classList.add(atPauseButtonClassName);
// Check to see if cookie is false or null.
if(animationPaused === "false" || animationPaused === null) {
btnPlayPause.setAttribute("aria-pressed", "false");
animationBody.classList.add(atEnabledClassName);
} else {
btnPlayPause.setAttribute("aria-pressed", "true");
animationBody.classList.remove(atEnabledClassName);
}
// Append pause button.
btnControls.append(btnPlayPause);
// Pause Toggle Event.
btnPlayPause.addEventListener("click", function() {
const getAtPauseButtonClass = document.querySelectorAll(atPauseButtonClass);
const animationPauseToggles = getAtPauseButtonClass;
if (this.getAttribute("aria-pressed") === "false") {
animationBody.classList.remove(atEnabledClassName);
setCookie("true");
animationPauseToggles.forEach(button => {
button.setAttribute("aria-pressed", "true");
});
} else {
animationBody.classList.add(atEnabledClassName);
setCookie("false");
animationPauseToggles.forEach(button => {
button.setAttribute("aria-pressed", "false");
});
}
getBackgroundVideos.forEach(video => {
if(!video.closest(atClass).querySelector(atPauseButtonClass).hasAttribute("disabled")) {
const isPaused = video.closest(atClass).querySelector(atPauseButtonClass).getAttribute("aria-pressed");
if(isPaused === "false") {
video.play();
} else {
video.pause();
}
}
});
if(this.closest(atClass).hasAttribute("data-callback")) {
const thisButton = this;
const callBackFunction = this.closest(atClass).getAttribute("data-callback");
customCallback(thisButton, callBackFunction);
}
});
if (wrapper.querySelector("track") !== null) {
const btnAudioDescription = document.createElement("button");
btnAudioDescription.setAttribute("aria-label", wrapper.hasAttribute(dataAudioDescriptionButton) ? wrapper.getAttribute(dataAudioDescriptionButton) : atAudioDescriptionLabel);
btnAudioDescription.setAttribute("aria-pressed", "false");
btnAudioDescription.setAttribute("class", atAudioDescriptionClassName);
btnControls.append(btnAudioDescription);
btnAudioDescription.addEventListener("click", function () {
const thisVideo = wrapper.querySelector(atVideoClass);
const thisDescription = wrapper.querySelector(atDescriptionTrackClass);
thisDescription.classList.toggle("active");
const isPressed = this.getAttribute("aria-pressed") === "true";
const track = thisVideo.textTracks ? thisVideo.textTracks[0] : null;
if (!isPressed) {
this.setAttribute("aria-pressed", "true");
if (track) {
track.mode = "hidden";
track.oncuechange = function () {
const currentCue = this.activeCues[0];
if (currentCue) {
thisDescription.innerText = "";
thisDescription.appendChild(currentCue.getCueAsHTML());
const Message = thisDescription.textContent;
const msg = new SpeechSynthesisUtterance(Message);
window.speechSynthesis.speak(msg);
}
};
}
} else {
this.setAttribute("aria-pressed", "false");
if (track) {
track.oncuechange = null;
}
thisDescription.innerText = "";
window.speechSynthesis.cancel();
}
});
}
});
getBackgroundVideos.forEach((video, e) => {
video.setAttribute("crossorigin", "anonymous")
video.setAttribute("disableRemotePlayback", "");
video.setAttribute("disablePictureInPicture", "");
video.id = `animation-toggle-video-${e + 1}`;
video.setAttribute("playsinline" , "");
video.muted = true;
video.load();
if (animationBody.classList.contains(atEnabledClassName)) {
if(!video.hasAttribute(dataAutoplayDisable)) {
video.onloadeddata = () => {
const playPromise = video.play();
if (playPromise !== undefined) {
playPromise.then(() => {
// Automatic playback started!
}).catch(error => {
console.error('Playback error:', error);
});
}
}
}
} else {
if(video.hasAttribute(dataPoster)) {
video.currentTime = video.getAttribute(dataPoster);
}
}
if(video.hasAttribute(dataLoop)) {
let iterations = 1;
video.addEventListener("ended", () => {
if (iterations < video.getAttribute(dataLoop)) {
video.currentTime = 0;
video.play();
iterations ++;
} else {
const toggleButton = video.closest(atClass).querySelector(atPauseButtonClass);
if(toggleButton) {
toggleButton.setAttribute("disabled", "");
}
}
}, false);
} else {
video.setAttribute("loop" , "");
}
if(!video.hasAttribute("aria-label")) {
video.setAttribute("aria-label", atVideoLabel);
}
if(video.hasAttribute(dataDecorative)) {
video.setAttribute("aria-hidden", "true");
video.setAttribute("tabindex", "-1");
video.removeAttribute("aria-label");
}
});
const customCallback = (thisButton, customCallBackName) => {
if (customCallBackName !== null) {
window[customCallBackName](thisButton);
}
};
atCallback.forEach(callback => {
const thisButton = callback.querySelector(atPauseButtonClass);
const callBackFunction = callback.dataset.callback;
customCallback(thisButton, callBackFunction);
});
});
};
initAnimationToggle();
})();Warning: In some situations, it may be desirable to remove a pause button but still have animation benefit from cookie setting enabled in videos that do contain a pause button. This is not a feature that should be used lightly.
<div class="animation-toggle" data-pause-remove>
<video class="animation-toggle__video" loading="lazy">
<source src="https://radancy.dev/component-library/animation-toggle/demo-lg.mp4">
</video>
</div>/*!
Radancy Component Library: Animation Toggle
Contributor(s):
Michael "Spell" Spellacy
Dependencies: Sass
*/
.animation-toggle {
display: grid;
// Control Container (Mandatory)
&__controls {
display: grid;
gap: calc(12rem / 16);
grid-auto-flow: column;
justify-self: end;
margin-block-start: calc(12rem / 16);
margin-inline-end: calc(12rem / 16);
z-index: 2;
}
// Toggle Buttons (Mandatory)
// Important: Be sure to adjust colors as needed so that button stands out on darker or lighter backgrounds.
&__pause, &__audio {
background-color: #000;
background-position: center;
background-repeat: no-repeat;
border-radius: calc(3em / 16);
border: calc(1rem / 16) #fff solid;
cursor: pointer;
height: calc(30rem / 16);
opacity: 0.5;
outline-offset: 0.25em;
transition: all 300ms ease-in-out;
width: calc(30rem / 16);
.animation-toggle:focus-within &, .animation-toggle:hover & {
opacity: 1;
}
}
&__pause {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAMAAAAp4XiDAAAAclBMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////9eWEHEAAAAJXRSTlMA8mB05+Tr4NqonE0f7si8sJWTdXNlXEk6MSTd0qKKf1UnFxEL+BREzAAAAKRJREFUSMftlscOg0AMBR/E9E5CTS/7/78YpA1klbUiwdlzfKM52wAedZjRjE/5DQZdQf5XHuMUE736JcJCZckESB1lUc3FVdkMqBVDqouXz7gIIZe4Okk4F8Dh5oNOXM45fLJfn+wkkUQSSSSRRJLt53XLEQ+5ufn7KsTcPOrkmTGuxJ1ZY3y4MLIHOmsssHC2ZIuJMQrImyE6NTBoczKkVw7AG83JGMcGlBNJAAAAAElFTkSuQmCC");
background-size: 0.625rem;
&[aria-pressed="true"] {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOoAAAE2AQMAAABV0ZztAAAABlBMVEUAAAD///+l2Z/dAAAAAXRSTlMAQObYZgAAAipJREFUaN7t2rFNBjEMhuFDFJSM8I/CaHg0j+IRXLqw/CEQBc29KSyoSPtId7kUucT+rutZccF4qInfJAOW6OlPkuqenyV6+Ysk3fOrRHN7fLIzJ322VMxzy++frAMbcwDT1L9UxTzMOrAxB3MyN/Mw68DOHMzJ3MzDrAM7czAXczMPs4zZmYO5mJt5mGXMzpzMxdzMOrAxO3MyF/Mw68DGHMzJXMzDrAMbczAnczMPsw7szMGczM08zDqwMwdzMTfzMMuYnTmYi7mZdWBjduZkLuZm1oGN2ZmTuZiHWQc25mBO5mIeZh3YmIM5mZt5mHVgZw7mZG7mYZYxO3MwF3MzD7OM2ZmDuZibWQc2ZmdO5mJuZh3YmJ05mYt5mHVgYw7mYm7mYZYxO3N8881I5mLuFQ+zdmy/yf7Pf77m14aHuVdczMkczM5szKutp5iT2Zlts2E3czEHs2/+gsNczMkczMa8OrcUczA7syHP6qyYzMFszKsDeDEnszMb8qwuPckczMa8uioWczI7s23u381czMHsm6rFMBdzMjuzMa/qTMUczM68qs41czIHszGvCqbFHMzObMizKlInczAb86q0X8zJ7My26Zc0czIHs2+6TMNczMnszIY8q75gMQezM6+6qcWczMFszKsGd1FiQQpmZ76Qh7mZkzkoBiIZ84Xcd/z4uaR3nMyO4RkZ8nAyp5kTYz8KDA3JmDmR1JxnSuCH5MAvGtDrSXGPH9boP57gj/lMAAAAAElFTkSuQmCC")
}
&[disabled] {
background-color: #333;
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAMAAAAp4XiDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDkuMC1jMDAwIDc5LjE3MWMyN2ZhYiwgMjAyMi8wOC8xNi0yMjozNTo0MSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIDI0LjAgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjFGNDQzOTFBMDNFNzExRUZBREE3RDY3NEY5MTNEMDlEIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjFGNDQzOTFCMDNFNzExRUZBREE3RDY3NEY5MTNEMDlEIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6MUY0NDM5MTgwM0U3MTFFRkFEQTdENjc0RjkxM0QwOUQiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6MUY0NDM5MTkwM0U3MTFFRkFEQTdENjc0RjkxM0QwOUQiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7n3NecAAAABlBMVEX///////9VfPVsAAAAAnRSTlP/AOW3MEoAAADVSURBVHjarNbbDsMgDAPQ5P9/epo2qQEnthetj4WzUciFSHjieJpxOr9VIQGgcMSJwgKHCVMUE654TPSCvMxmcN71SvjRXYMhBQwHXxagL1FBVad8iAgpMA3h5k2emd4eFHKteP6bg1AzLcYxEEBWFFxxOhpNpBmCwyOzISfupA6UHfWa/p5NUnyk2J35YLTRSTLHkhNTkDGmGfKFFhYkwtRETrPqtRUmvVS0q8RZLX8xWJPTLeOLZrFpSVP1Z41v0143TXxzVeDmj9eezeXKusK9BBgAWdwE4I4jwMQAAAAASUVORK5CYII=");
cursor: default;
}
[data-pause-remove] & {
display: none;
}
}
&__audio {
background-image: url("data:image/png;base64,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");
background-size: calc(18rem / 16);
}
// Video (Optional)
&__video {
width: 100%;
}
// Backdrop (Optional)
&__backdrop {
&:not(.animation-enabled &) {
&::after {
background-color: rgba(0, 0, 0, 0.5);
content: "";
pointer-events: none;
position: relative;
z-index: 1;
}
}
}
// Primary Content (Optional)
&__content {
color: #fff;
font-size: clamp(1rem, 9vw, 6rem);
font-weight: bold;
line-height: normal;
place-self: center;
text-align: center;
z-index: 2;
}
// Audio Description Track (Optional)
&__track {
align-self: end;
background-color: rgba(0,0,0,.8);
border-radius: 0.3125em;
color: #fff;
display: none;
font-size: 0.9rem;
line-height: normal;
margin: 1em;
padding: 0.7em;
text-align: center;
z-index: 1;
@media(min-width: 42.5em) {
font-size: 1.1rem;
justify-self: center;
}
&.active {
display: block;
&:empty {
display: none;
}
}
}
// Shared
&__controls, &__video, &__content, &__track, &__backdrop::after {
grid-area: 1 / 1;
}
}
/*!
Radancy Component Library: Animation Toggle
Contributor(s):
Michael "Spell" Spellacy
Dependencies: None
*/
(() => {
"use strict";
const loadLanguagePack = (url, callback) => {
// Install Language Pack.
const getComponentLanguagePack = document.getElementById("component-library-language-pack");
if (!getComponentLanguagePack) {
const componentLanguagePack = document.createElement("script");
componentLanguagePack.setAttribute("src", url);
componentLanguagePack.setAttribute("id", "component-library-language-pack");
componentLanguagePack.addEventListener("load", callback);
document.head.appendChild(componentLanguagePack);
} else {
getComponentLanguagePack.addEventListener("load", callback);
}
};
const initAnimationToggle = () => {
loadLanguagePack("https://services.tmpwebeng.com/component-library/language-pack.js", () => {
// Display which component in use via console:
console.log("%cAnimation Toggle%cv2.9.0", "background: #2d2d2d; color: #fff; padding: 6px 10px; border-radius: 16px 0 0 16px; font-weight: 600;" , "background: #6e00ee; color: #fff; padding: 6px 10px; border-radius: 0 16px 16px 0; font-weight: 600;");
// Animation variables
const animationBody = document.body;
const atAudioDescriptionClassName = "animation-toggle__audio";
const atClass = ".animation-toggle";
const atCallback = document.querySelectorAll(`${atClass}[data-callback]`);
const atCookieName = "AnimationPaused";
const atDescriptionTrackClass = ".animation-toggle__track";
const atEnabledClassName = "animation-enabled";
const atPauseButtonClassName = "animation-toggle__pause";
const atPauseButtonClass = `.${atPauseButtonClassName}`;
const atVideoClass = ".animation-toggle__video";
const atVideoControlsName = "animation-toggle__controls";
const dataAudioDescriptionButton = "data-audio-description-button";
const dataAutoplayDisable = "data-autoplay-disable";
const dataDecorative = "data-decorative";
const dataLoop = "data-loop";
const dataPauseButton = "data-pause-button";
const dataPoster = "data-poster";
const getAnimationWrappers = document.querySelectorAll(atClass);
const getBackgroundVideos = document.querySelectorAll(atVideoClass);
// Language
const atAudioDescriptionLabel = window.atAudioDescriptionLabel;
const atPauseButtonLabel = window.atPauseButtonLabel;
const atVideoLabel = window.atVideoLabel;
// Used to retrieve cookie and pause video(s) if present.
const getCookie = name => {
const match = document.cookie.match(RegExp(`(?:^|;\\s*)${name}=([^;]*)`));
return match ? match[1] : null;
};
// Assign cookie to variable on page load.
let animationPaused = getCookie(atCookieName);
// Used to set and remove cookie.
const setCookie = state => {
document.cookie = `${atCookieName}=${state}; SameSite=None; Secure; path=/`;
};
// If animation disabled in OS settings and cookie is not present then force pause.
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
if(animationPaused === null) {
setCookie("true");
animationPaused = getCookie(atCookieName);
}
}
// For each animation wrapper...
getAnimationWrappers.forEach(wrapper => {
// Create control wrapper.
const btnControls = document.createElement("div");
btnControls.setAttribute("class", atVideoControlsName);
// Prepend control wrapper to atClass.
wrapper.prepend(btnControls);
// Create pause button
const btnPlayPause = document.createElement("button");
// See if wrapper contains custom pause button value; use over default if true.
btnPlayPause.setAttribute("aria-label", wrapper.hasAttribute(dataPauseButton) ? wrapper.getAttribute(dataPauseButton) : atPauseButtonLabel);
// Add class to pause button.
btnPlayPause.classList.add(atPauseButtonClassName);
// Check to see if cookie is false or null.
if(animationPaused === "false" || animationPaused === null) {
btnPlayPause.setAttribute("aria-pressed", "false");
animationBody.classList.add(atEnabledClassName);
} else {
btnPlayPause.setAttribute("aria-pressed", "true");
animationBody.classList.remove(atEnabledClassName);
}
// Append pause button.
btnControls.append(btnPlayPause);
// Pause Toggle Event.
btnPlayPause.addEventListener("click", function() {
const getAtPauseButtonClass = document.querySelectorAll(atPauseButtonClass);
const animationPauseToggles = getAtPauseButtonClass;
if (this.getAttribute("aria-pressed") === "false") {
animationBody.classList.remove(atEnabledClassName);
setCookie("true");
animationPauseToggles.forEach(button => {
button.setAttribute("aria-pressed", "true");
});
} else {
animationBody.classList.add(atEnabledClassName);
setCookie("false");
animationPauseToggles.forEach(button => {
button.setAttribute("aria-pressed", "false");
});
}
getBackgroundVideos.forEach(video => {
if(!video.closest(atClass).querySelector(atPauseButtonClass).hasAttribute("disabled")) {
const isPaused = video.closest(atClass).querySelector(atPauseButtonClass).getAttribute("aria-pressed");
if(isPaused === "false") {
video.play();
} else {
video.pause();
}
}
});
if(this.closest(atClass).hasAttribute("data-callback")) {
const thisButton = this;
const callBackFunction = this.closest(atClass).getAttribute("data-callback");
customCallback(thisButton, callBackFunction);
}
});
if (wrapper.querySelector("track") !== null) {
const btnAudioDescription = document.createElement("button");
btnAudioDescription.setAttribute("aria-label", wrapper.hasAttribute(dataAudioDescriptionButton) ? wrapper.getAttribute(dataAudioDescriptionButton) : atAudioDescriptionLabel);
btnAudioDescription.setAttribute("aria-pressed", "false");
btnAudioDescription.setAttribute("class", atAudioDescriptionClassName);
btnControls.append(btnAudioDescription);
btnAudioDescription.addEventListener("click", function () {
const thisVideo = wrapper.querySelector(atVideoClass);
const thisDescription = wrapper.querySelector(atDescriptionTrackClass);
thisDescription.classList.toggle("active");
const isPressed = this.getAttribute("aria-pressed") === "true";
const track = thisVideo.textTracks ? thisVideo.textTracks[0] : null;
if (!isPressed) {
this.setAttribute("aria-pressed", "true");
if (track) {
track.mode = "hidden";
track.oncuechange = function () {
const currentCue = this.activeCues[0];
if (currentCue) {
thisDescription.innerText = "";
thisDescription.appendChild(currentCue.getCueAsHTML());
const Message = thisDescription.textContent;
const msg = new SpeechSynthesisUtterance(Message);
window.speechSynthesis.speak(msg);
}
};
}
} else {
this.setAttribute("aria-pressed", "false");
if (track) {
track.oncuechange = null;
}
thisDescription.innerText = "";
window.speechSynthesis.cancel();
}
});
}
});
getBackgroundVideos.forEach((video, e) => {
video.setAttribute("crossorigin", "anonymous")
video.setAttribute("disableRemotePlayback", "");
video.setAttribute("disablePictureInPicture", "");
video.id = `animation-toggle-video-${e + 1}`;
video.setAttribute("playsinline" , "");
video.muted = true;
video.load();
if (animationBody.classList.contains(atEnabledClassName)) {
if(!video.hasAttribute(dataAutoplayDisable)) {
video.onloadeddata = () => {
const playPromise = video.play();
if (playPromise !== undefined) {
playPromise.then(() => {
// Automatic playback started!
}).catch(error => {
console.error('Playback error:', error);
});
}
}
}
} else {
if(video.hasAttribute(dataPoster)) {
video.currentTime = video.getAttribute(dataPoster);
}
}
if(video.hasAttribute(dataLoop)) {
let iterations = 1;
video.addEventListener("ended", () => {
if (iterations < video.getAttribute(dataLoop)) {
video.currentTime = 0;
video.play();
iterations ++;
} else {
const toggleButton = video.closest(atClass).querySelector(atPauseButtonClass);
if(toggleButton) {
toggleButton.setAttribute("disabled", "");
}
}
}, false);
} else {
video.setAttribute("loop" , "");
}
if(!video.hasAttribute("aria-label")) {
video.setAttribute("aria-label", atVideoLabel);
}
if(video.hasAttribute(dataDecorative)) {
video.setAttribute("aria-hidden", "true");
video.setAttribute("tabindex", "-1");
video.removeAttribute("aria-label");
}
});
const customCallback = (thisButton, customCallBackName) => {
if (customCallBackName !== null) {
window[customCallBackName](thisButton);
}
};
atCallback.forEach(callback => {
const thisButton = callback.querySelector(atPauseButtonClass);
const callBackFunction = callback.dataset.callback;
customCallback(thisButton, callBackFunction);
});
});
};
initAnimationToggle();
})();Note: Pause the video and then refresh the page. The first frame is from 25 seconds in. Please note that when pressing play, video will resume from this point.
<div class="animation-toggle">
<video class="animation-toggle__video" data-poster="25" loading="lazy">
<source src="https://radancy.dev/component-library/animation-toggle/demo-lg.mp4">
</video>
</div>/*!
Radancy Component Library: Animation Toggle
Contributor(s):
Michael "Spell" Spellacy
Dependencies: Sass
*/
.animation-toggle {
display: grid;
// Control Container (Mandatory)
&__controls {
display: grid;
gap: calc(12rem / 16);
grid-auto-flow: column;
justify-self: end;
margin-block-start: calc(12rem / 16);
margin-inline-end: calc(12rem / 16);
z-index: 2;
}
// Toggle Buttons (Mandatory)
// Important: Be sure to adjust colors as needed so that button stands out on darker or lighter backgrounds.
&__pause, &__audio {
background-color: #000;
background-position: center;
background-repeat: no-repeat;
border-radius: calc(3em / 16);
border: calc(1rem / 16) #fff solid;
cursor: pointer;
height: calc(30rem / 16);
opacity: 0.5;
outline-offset: 0.25em;
transition: all 300ms ease-in-out;
width: calc(30rem / 16);
.animation-toggle:focus-within &, .animation-toggle:hover & {
opacity: 1;
}
}
&__pause {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAMAAAAp4XiDAAAAclBMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////9eWEHEAAAAJXRSTlMA8mB05+Tr4NqonE0f7si8sJWTdXNlXEk6MSTd0qKKf1UnFxEL+BREzAAAAKRJREFUSMftlscOg0AMBR/E9E5CTS/7/78YpA1klbUiwdlzfKM52wAedZjRjE/5DQZdQf5XHuMUE736JcJCZckESB1lUc3FVdkMqBVDqouXz7gIIZe4Okk4F8Dh5oNOXM45fLJfn+wkkUQSSSSRRJLt53XLEQ+5ufn7KsTcPOrkmTGuxJ1ZY3y4MLIHOmsssHC2ZIuJMQrImyE6NTBoczKkVw7AG83JGMcGlBNJAAAAAElFTkSuQmCC");
background-size: 0.625rem;
&[aria-pressed="true"] {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOoAAAE2AQMAAABV0ZztAAAABlBMVEUAAAD///+l2Z/dAAAAAXRSTlMAQObYZgAAAipJREFUaN7t2rFNBjEMhuFDFJSM8I/CaHg0j+IRXLqw/CEQBc29KSyoSPtId7kUucT+rutZccF4qInfJAOW6OlPkuqenyV6+Ysk3fOrRHN7fLIzJ322VMxzy++frAMbcwDT1L9UxTzMOrAxB3MyN/Mw68DOHMzJ3MzDrAM7czAXczMPs4zZmYO5mJt5mGXMzpzMxdzMOrAxO3MyF/Mw68DGHMzJXMzDrAMbczAnczMPsw7szMGczM08zDqwMwdzMTfzMMuYnTmYi7mZdWBjduZkLuZm1oGN2ZmTuZiHWQc25mBO5mIeZh3YmIM5mZt5mHVgZw7mZG7mYZYxO3MwF3MzD7OM2ZmDuZibWQc2ZmdO5mJuZh3YmJ05mYt5mHVgYw7mYm7mYZYxO3N8881I5mLuFQ+zdmy/yf7Pf77m14aHuVdczMkczM5szKutp5iT2Zlts2E3czEHs2/+gsNczMkczMa8OrcUczA7syHP6qyYzMFszKsDeDEnszMb8qwuPckczMa8uioWczI7s23u381czMHsm6rFMBdzMjuzMa/qTMUczM68qs41czIHszGvCqbFHMzObMizKlInczAb86q0X8zJ7My26Zc0czIHs2+6TMNczMnszIY8q75gMQezM6+6qcWczMFszKsGd1FiQQpmZ76Qh7mZkzkoBiIZ84Xcd/z4uaR3nMyO4RkZ8nAyp5kTYz8KDA3JmDmR1JxnSuCH5MAvGtDrSXGPH9boP57gj/lMAAAAAElFTkSuQmCC")
}
&[disabled] {
background-color: #333;
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAMAAAAp4XiDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDkuMC1jMDAwIDc5LjE3MWMyN2ZhYiwgMjAyMi8wOC8xNi0yMjozNTo0MSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIDI0LjAgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjFGNDQzOTFBMDNFNzExRUZBREE3RDY3NEY5MTNEMDlEIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjFGNDQzOTFCMDNFNzExRUZBREE3RDY3NEY5MTNEMDlEIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6MUY0NDM5MTgwM0U3MTFFRkFEQTdENjc0RjkxM0QwOUQiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6MUY0NDM5MTkwM0U3MTFFRkFEQTdENjc0RjkxM0QwOUQiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7n3NecAAAABlBMVEX///////9VfPVsAAAAAnRSTlP/AOW3MEoAAADVSURBVHjarNbbDsMgDAPQ5P9/epo2qQEnthetj4WzUciFSHjieJpxOr9VIQGgcMSJwgKHCVMUE654TPSCvMxmcN71SvjRXYMhBQwHXxagL1FBVad8iAgpMA3h5k2emd4eFHKteP6bg1AzLcYxEEBWFFxxOhpNpBmCwyOzISfupA6UHfWa/p5NUnyk2J35YLTRSTLHkhNTkDGmGfKFFhYkwtRETrPqtRUmvVS0q8RZLX8xWJPTLeOLZrFpSVP1Z41v0143TXxzVeDmj9eezeXKusK9BBgAWdwE4I4jwMQAAAAASUVORK5CYII=");
cursor: default;
}
[data-pause-remove] & {
display: none;
}
}
&__audio {
background-image: url("data:image/png;base64,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");
background-size: calc(18rem / 16);
}
// Video (Optional)
&__video {
width: 100%;
}
// Backdrop (Optional)
&__backdrop {
&:not(.animation-enabled &) {
&::after {
background-color: rgba(0, 0, 0, 0.5);
content: "";
pointer-events: none;
position: relative;
z-index: 1;
}
}
}
// Primary Content (Optional)
&__content {
color: #fff;
font-size: clamp(1rem, 9vw, 6rem);
font-weight: bold;
line-height: normal;
place-self: center;
text-align: center;
z-index: 2;
}
// Audio Description Track (Optional)
&__track {
align-self: end;
background-color: rgba(0,0,0,.8);
border-radius: 0.3125em;
color: #fff;
display: none;
font-size: 0.9rem;
line-height: normal;
margin: 1em;
padding: 0.7em;
text-align: center;
z-index: 1;
@media(min-width: 42.5em) {
font-size: 1.1rem;
justify-self: center;
}
&.active {
display: block;
&:empty {
display: none;
}
}
}
// Shared
&__controls, &__video, &__content, &__track, &__backdrop::after {
grid-area: 1 / 1;
}
}
/*!
Radancy Component Library: Animation Toggle
Contributor(s):
Michael "Spell" Spellacy
Dependencies: None
*/
(() => {
"use strict";
const loadLanguagePack = (url, callback) => {
// Install Language Pack.
const getComponentLanguagePack = document.getElementById("component-library-language-pack");
if (!getComponentLanguagePack) {
const componentLanguagePack = document.createElement("script");
componentLanguagePack.setAttribute("src", url);
componentLanguagePack.setAttribute("id", "component-library-language-pack");
componentLanguagePack.addEventListener("load", callback);
document.head.appendChild(componentLanguagePack);
} else {
getComponentLanguagePack.addEventListener("load", callback);
}
};
const initAnimationToggle = () => {
loadLanguagePack("https://services.tmpwebeng.com/component-library/language-pack.js", () => {
// Display which component in use via console:
console.log("%cAnimation Toggle%cv2.9.0", "background: #2d2d2d; color: #fff; padding: 6px 10px; border-radius: 16px 0 0 16px; font-weight: 600;" , "background: #6e00ee; color: #fff; padding: 6px 10px; border-radius: 0 16px 16px 0; font-weight: 600;");
// Animation variables
const animationBody = document.body;
const atAudioDescriptionClassName = "animation-toggle__audio";
const atClass = ".animation-toggle";
const atCallback = document.querySelectorAll(`${atClass}[data-callback]`);
const atCookieName = "AnimationPaused";
const atDescriptionTrackClass = ".animation-toggle__track";
const atEnabledClassName = "animation-enabled";
const atPauseButtonClassName = "animation-toggle__pause";
const atPauseButtonClass = `.${atPauseButtonClassName}`;
const atVideoClass = ".animation-toggle__video";
const atVideoControlsName = "animation-toggle__controls";
const dataAudioDescriptionButton = "data-audio-description-button";
const dataAutoplayDisable = "data-autoplay-disable";
const dataDecorative = "data-decorative";
const dataLoop = "data-loop";
const dataPauseButton = "data-pause-button";
const dataPoster = "data-poster";
const getAnimationWrappers = document.querySelectorAll(atClass);
const getBackgroundVideos = document.querySelectorAll(atVideoClass);
// Language
const atAudioDescriptionLabel = window.atAudioDescriptionLabel;
const atPauseButtonLabel = window.atPauseButtonLabel;
const atVideoLabel = window.atVideoLabel;
// Used to retrieve cookie and pause video(s) if present.
const getCookie = name => {
const match = document.cookie.match(RegExp(`(?:^|;\\s*)${name}=([^;]*)`));
return match ? match[1] : null;
};
// Assign cookie to variable on page load.
let animationPaused = getCookie(atCookieName);
// Used to set and remove cookie.
const setCookie = state => {
document.cookie = `${atCookieName}=${state}; SameSite=None; Secure; path=/`;
};
// If animation disabled in OS settings and cookie is not present then force pause.
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
if(animationPaused === null) {
setCookie("true");
animationPaused = getCookie(atCookieName);
}
}
// For each animation wrapper...
getAnimationWrappers.forEach(wrapper => {
// Create control wrapper.
const btnControls = document.createElement("div");
btnControls.setAttribute("class", atVideoControlsName);
// Prepend control wrapper to atClass.
wrapper.prepend(btnControls);
// Create pause button
const btnPlayPause = document.createElement("button");
// See if wrapper contains custom pause button value; use over default if true.
btnPlayPause.setAttribute("aria-label", wrapper.hasAttribute(dataPauseButton) ? wrapper.getAttribute(dataPauseButton) : atPauseButtonLabel);
// Add class to pause button.
btnPlayPause.classList.add(atPauseButtonClassName);
// Check to see if cookie is false or null.
if(animationPaused === "false" || animationPaused === null) {
btnPlayPause.setAttribute("aria-pressed", "false");
animationBody.classList.add(atEnabledClassName);
} else {
btnPlayPause.setAttribute("aria-pressed", "true");
animationBody.classList.remove(atEnabledClassName);
}
// Append pause button.
btnControls.append(btnPlayPause);
// Pause Toggle Event.
btnPlayPause.addEventListener("click", function() {
const getAtPauseButtonClass = document.querySelectorAll(atPauseButtonClass);
const animationPauseToggles = getAtPauseButtonClass;
if (this.getAttribute("aria-pressed") === "false") {
animationBody.classList.remove(atEnabledClassName);
setCookie("true");
animationPauseToggles.forEach(button => {
button.setAttribute("aria-pressed", "true");
});
} else {
animationBody.classList.add(atEnabledClassName);
setCookie("false");
animationPauseToggles.forEach(button => {
button.setAttribute("aria-pressed", "false");
});
}
getBackgroundVideos.forEach(video => {
if(!video.closest(atClass).querySelector(atPauseButtonClass).hasAttribute("disabled")) {
const isPaused = video.closest(atClass).querySelector(atPauseButtonClass).getAttribute("aria-pressed");
if(isPaused === "false") {
video.play();
} else {
video.pause();
}
}
});
if(this.closest(atClass).hasAttribute("data-callback")) {
const thisButton = this;
const callBackFunction = this.closest(atClass).getAttribute("data-callback");
customCallback(thisButton, callBackFunction);
}
});
if (wrapper.querySelector("track") !== null) {
const btnAudioDescription = document.createElement("button");
btnAudioDescription.setAttribute("aria-label", wrapper.hasAttribute(dataAudioDescriptionButton) ? wrapper.getAttribute(dataAudioDescriptionButton) : atAudioDescriptionLabel);
btnAudioDescription.setAttribute("aria-pressed", "false");
btnAudioDescription.setAttribute("class", atAudioDescriptionClassName);
btnControls.append(btnAudioDescription);
btnAudioDescription.addEventListener("click", function () {
const thisVideo = wrapper.querySelector(atVideoClass);
const thisDescription = wrapper.querySelector(atDescriptionTrackClass);
thisDescription.classList.toggle("active");
const isPressed = this.getAttribute("aria-pressed") === "true";
const track = thisVideo.textTracks ? thisVideo.textTracks[0] : null;
if (!isPressed) {
this.setAttribute("aria-pressed", "true");
if (track) {
track.mode = "hidden";
track.oncuechange = function () {
const currentCue = this.activeCues[0];
if (currentCue) {
thisDescription.innerText = "";
thisDescription.appendChild(currentCue.getCueAsHTML());
const Message = thisDescription.textContent;
const msg = new SpeechSynthesisUtterance(Message);
window.speechSynthesis.speak(msg);
}
};
}
} else {
this.setAttribute("aria-pressed", "false");
if (track) {
track.oncuechange = null;
}
thisDescription.innerText = "";
window.speechSynthesis.cancel();
}
});
}
});
getBackgroundVideos.forEach((video, e) => {
video.setAttribute("crossorigin", "anonymous")
video.setAttribute("disableRemotePlayback", "");
video.setAttribute("disablePictureInPicture", "");
video.id = `animation-toggle-video-${e + 1}`;
video.setAttribute("playsinline" , "");
video.muted = true;
video.load();
if (animationBody.classList.contains(atEnabledClassName)) {
if(!video.hasAttribute(dataAutoplayDisable)) {
video.onloadeddata = () => {
const playPromise = video.play();
if (playPromise !== undefined) {
playPromise.then(() => {
// Automatic playback started!
}).catch(error => {
console.error('Playback error:', error);
});
}
}
}
} else {
if(video.hasAttribute(dataPoster)) {
video.currentTime = video.getAttribute(dataPoster);
}
}
if(video.hasAttribute(dataLoop)) {
let iterations = 1;
video.addEventListener("ended", () => {
if (iterations < video.getAttribute(dataLoop)) {
video.currentTime = 0;
video.play();
iterations ++;
} else {
const toggleButton = video.closest(atClass).querySelector(atPauseButtonClass);
if(toggleButton) {
toggleButton.setAttribute("disabled", "");
}
}
}, false);
} else {
video.setAttribute("loop" , "");
}
if(!video.hasAttribute("aria-label")) {
video.setAttribute("aria-label", atVideoLabel);
}
if(video.hasAttribute(dataDecorative)) {
video.setAttribute("aria-hidden", "true");
video.setAttribute("tabindex", "-1");
video.removeAttribute("aria-label");
}
});
const customCallback = (thisButton, customCallBackName) => {
if (customCallBackName !== null) {
window[customCallBackName](thisButton);
}
};
atCallback.forEach(callback => {
const thisButton = callback.querySelector(atPauseButtonClass);
const callBackFunction = callback.dataset.callback;
customCallback(thisButton, callBackFunction);
});
});
};
initAnimationToggle();
})();Note: Resize your browser window and refresh page. Loading alternatively sized and/or video formats to improve layout and performance is fully supported.
<div class="animation-toggle">
<video class="animation-toggle__video" loading="lazy">
<source media="(min-width: 60em)" src="https://radancy.dev/component-library/animation-toggle/demo-lg.webm">
<source media="(min-width: 60em)" src="https://radancy.dev/component-library/animation-toggle/demo-lg.mp4">
<source src="https://radancy.dev/component-library/animation-toggle/demo-sm.webm">
<source src="https://radancy.dev/component-library/animation-toggle/demo-sm.mp4">
</video>
</div>/*!
Radancy Component Library: Animation Toggle
Contributor(s):
Michael "Spell" Spellacy
Dependencies: Sass
*/
.animation-toggle {
display: grid;
// Control Container (Mandatory)
&__controls {
display: grid;
gap: calc(12rem / 16);
grid-auto-flow: column;
justify-self: end;
margin-block-start: calc(12rem / 16);
margin-inline-end: calc(12rem / 16);
z-index: 2;
}
// Toggle Buttons (Mandatory)
// Important: Be sure to adjust colors as needed so that button stands out on darker or lighter backgrounds.
&__pause, &__audio {
background-color: #000;
background-position: center;
background-repeat: no-repeat;
border-radius: calc(3em / 16);
border: calc(1rem / 16) #fff solid;
cursor: pointer;
height: calc(30rem / 16);
opacity: 0.5;
outline-offset: 0.25em;
transition: all 300ms ease-in-out;
width: calc(30rem / 16);
.animation-toggle:focus-within &, .animation-toggle:hover & {
opacity: 1;
}
}
&__pause {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAMAAAAp4XiDAAAAclBMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////9eWEHEAAAAJXRSTlMA8mB05+Tr4NqonE0f7si8sJWTdXNlXEk6MSTd0qKKf1UnFxEL+BREzAAAAKRJREFUSMftlscOg0AMBR/E9E5CTS/7/78YpA1klbUiwdlzfKM52wAedZjRjE/5DQZdQf5XHuMUE736JcJCZckESB1lUc3FVdkMqBVDqouXz7gIIZe4Okk4F8Dh5oNOXM45fLJfn+wkkUQSSSSRRJLt53XLEQ+5ufn7KsTcPOrkmTGuxJ1ZY3y4MLIHOmsssHC2ZIuJMQrImyE6NTBoczKkVw7AG83JGMcGlBNJAAAAAElFTkSuQmCC");
background-size: 0.625rem;
&[aria-pressed="true"] {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOoAAAE2AQMAAABV0ZztAAAABlBMVEUAAAD///+l2Z/dAAAAAXRSTlMAQObYZgAAAipJREFUaN7t2rFNBjEMhuFDFJSM8I/CaHg0j+IRXLqw/CEQBc29KSyoSPtId7kUucT+rutZccF4qInfJAOW6OlPkuqenyV6+Ysk3fOrRHN7fLIzJ322VMxzy++frAMbcwDT1L9UxTzMOrAxB3MyN/Mw68DOHMzJ3MzDrAM7czAXczMPs4zZmYO5mJt5mGXMzpzMxdzMOrAxO3MyF/Mw68DGHMzJXMzDrAMbczAnczMPsw7szMGczM08zDqwMwdzMTfzMMuYnTmYi7mZdWBjduZkLuZm1oGN2ZmTuZiHWQc25mBO5mIeZh3YmIM5mZt5mHVgZw7mZG7mYZYxO3MwF3MzD7OM2ZmDuZibWQc2ZmdO5mJuZh3YmJ05mYt5mHVgYw7mYm7mYZYxO3N8881I5mLuFQ+zdmy/yf7Pf77m14aHuVdczMkczM5szKutp5iT2Zlts2E3czEHs2/+gsNczMkczMa8OrcUczA7syHP6qyYzMFszKsDeDEnszMb8qwuPckczMa8uioWczI7s23u381czMHsm6rFMBdzMjuzMa/qTMUczM68qs41czIHszGvCqbFHMzObMizKlInczAb86q0X8zJ7My26Zc0czIHs2+6TMNczMnszIY8q75gMQezM6+6qcWczMFszKsGd1FiQQpmZ76Qh7mZkzkoBiIZ84Xcd/z4uaR3nMyO4RkZ8nAyp5kTYz8KDA3JmDmR1JxnSuCH5MAvGtDrSXGPH9boP57gj/lMAAAAAElFTkSuQmCC")
}
&[disabled] {
background-color: #333;
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAMAAAAp4XiDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDkuMC1jMDAwIDc5LjE3MWMyN2ZhYiwgMjAyMi8wOC8xNi0yMjozNTo0MSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIDI0LjAgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjFGNDQzOTFBMDNFNzExRUZBREE3RDY3NEY5MTNEMDlEIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjFGNDQzOTFCMDNFNzExRUZBREE3RDY3NEY5MTNEMDlEIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6MUY0NDM5MTgwM0U3MTFFRkFEQTdENjc0RjkxM0QwOUQiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6MUY0NDM5MTkwM0U3MTFFRkFEQTdENjc0RjkxM0QwOUQiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7n3NecAAAABlBMVEX///////9VfPVsAAAAAnRSTlP/AOW3MEoAAADVSURBVHjarNbbDsMgDAPQ5P9/epo2qQEnthetj4WzUciFSHjieJpxOr9VIQGgcMSJwgKHCVMUE654TPSCvMxmcN71SvjRXYMhBQwHXxagL1FBVad8iAgpMA3h5k2emd4eFHKteP6bg1AzLcYxEEBWFFxxOhpNpBmCwyOzISfupA6UHfWa/p5NUnyk2J35YLTRSTLHkhNTkDGmGfKFFhYkwtRETrPqtRUmvVS0q8RZLX8xWJPTLeOLZrFpSVP1Z41v0143TXxzVeDmj9eezeXKusK9BBgAWdwE4I4jwMQAAAAASUVORK5CYII=");
cursor: default;
}
[data-pause-remove] & {
display: none;
}
}
&__audio {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAMAAAAp4XiDAAACQ1BMVEUAAACJiYn///+hoaH////09PRKSkq4uLicnJz9/f35+fn5+fnx8fHr6+vp6enl5eXGxsa4uLjMzMzHx8fV1dXHx8eIiIicnJx5eXmDg4OUlJSCgoL9/f329vb29vb29vbs7Ozx8fHr6+vv7+/h4eH////m5ubh4eHd3d3a2trQ0NDw8PDU1NTc3NzPz8/i4uLa2trDw8PPz8/j4+PY2NjT09Pj4+Pe3t7b29vPz8/Y2NjQ0NC4uLjd3d3j4+PV1dW7u7uLi4vNzc3FxcWrq6vMzMzHx8e3t7ednZ2ioqKKiorFxcWdnZ17e3u1tbWmpqalpaV4eHh2dnacnJy/v7+8vLysrKx4eHjQ0NCIiIi0tLRqamp+fn68vLy+vr41NTXt7e37+/v5+fn19fX4+Pjh4eH////z8/Pj4+P////////l5eXh4eHy8vLq6uru7u7e3t7U1NT19fXi4uLR0dHt7e3g4ODY2Njf39/u7u7q6urp6enOzs7w8PDb29vU1NTs7OzAwMDNzc3Gxsbn5+fKysrd3d3X19f///+vr6+UlJTz8/PS0tL///+pqam5ubnW1ta3t7fa2trCwsKqqqr///+kpKSgoKC3t7eXl5fY2NjDw8OkpKTp6em7u7uxsbGEhITR0dHo6OjBwcGYmJizs7OLi4u2trbIyMiKioqPj4+dnZ2+vr5lZWWnp6eYmJiHh4elpaWPj49QUFBPT089PT0xMTG9vb1qamo9PT0vLy+oqKj////9/f38/Pz29vby8vJp7g+xAAAAvHRSTlMAadiJkfETbgr8+u/r5tDKlJJzaV9bOSoVEA4G+PXp493b1tLR0M7Ix8S2tbWxr66sq6qmoqGbm5mXlZGMiX11cnJtbWlmZWJhWFhUUkpJRz8+NjUxJiMhHx8cHBgWDwX9+/Pz6efm4+Df3NjW09HOzczFw8G/v7++vLe0tLCrp6Oin5qUlJKQjo2KiYmIiIaFg4KCgoB+fXx8eXVzZ2NiYFxbWVdUU1JGRD88Ojg1Mi4tLSwnGBYUDgsJBDauXZEAAAKXSURBVEjHrZZnVxNBGEbHkoQivUlHQFAQkC6giFQpAgKCNLH33nvvvffee+++QX+aM89MsqtusmeP3E/32ZN7sslm94QNLaMjmi0njoKqHqtReBKlV8dYjEKJbCH91po3iURxN6w1PhuIaE2Hyau6a+/P0s1dxKn1nsSEL6WF+Y3Rrp0pmgvMhEoiii99p1aQaA6aNZ2BROQsicUY8BfNaWbGJuIkRsLbSNBg2uwkzs978GNoehl4NcLN8TOXqhqjtGYcXncLjlNLY+Aa/UVghY9KBubjwAzh0zVlw+gfbDWqmSm3XXiCsJWGCZiomlysjUIvQl94TGi9TOxyia/t46CwMn3yay75OrUmVzbZGFuFJgsL0CdF9s/27pY8d9OE5BncGc11PzRKl5xioJkUAZhf/TDquDpgT3TJSCa5S4rnmNvgwdzaYQ6DhK0lyWSsQ/AcbrNhU4ySqSTJxzoPH8+tf56wUqOkniQTsG7Cx3L7ZhNWbJQ8JkkG1jR38h1JkVFS+ccvoNx9Yl/w5ZUYJdkkOYx1RJ6k14//gRQPMbPgk7i9hYXqkqPqUbFI7TjMWBtGBdc7sAe65AQeWjWLSXEFSaQcXVyDYa91yah1aUHJ8eRiDAMhGKnue3RBLBIj/D/JxBernlsfdB/zlCzpksU57RfqgDZ5Svaou79PzjbhK3D22uXV48xoZYrVOHBZewyE4XDDsuFulqfkld3GE1z32QvgqfJNTChXV1F7JrWaFGfl1Ybb/XAHmRQHiLPqpRzpYmz2HkSliMtZDVcXPshr0L6dKKn4qWteFcUWr0Xv9cKwDph2Z+9lFjg5SDQYZiH4kUNEOzotFO8TiAIfWQh6dhMVtlgIosOzMiPmWPxzVYfg//kNYqtcGTkX0/4AAAAASUVORK5CYII=");
background-size: calc(18rem / 16);
}
// Video (Optional)
&__video {
width: 100%;
}
// Backdrop (Optional)
&__backdrop {
&:not(.animation-enabled &) {
&::after {
background-color: rgba(0, 0, 0, 0.5);
content: "";
pointer-events: none;
position: relative;
z-index: 1;
}
}
}
// Primary Content (Optional)
&__content {
color: #fff;
font-size: clamp(1rem, 9vw, 6rem);
font-weight: bold;
line-height: normal;
place-self: center;
text-align: center;
z-index: 2;
}
// Audio Description Track (Optional)
&__track {
align-self: end;
background-color: rgba(0,0,0,.8);
border-radius: 0.3125em;
color: #fff;
display: none;
font-size: 0.9rem;
line-height: normal;
margin: 1em;
padding: 0.7em;
text-align: center;
z-index: 1;
@media(min-width: 42.5em) {
font-size: 1.1rem;
justify-self: center;
}
&.active {
display: block;
&:empty {
display: none;
}
}
}
// Shared
&__controls, &__video, &__content, &__track, &__backdrop::after {
grid-area: 1 / 1;
}
}
/*!
Radancy Component Library: Animation Toggle
Contributor(s):
Michael "Spell" Spellacy
Dependencies: None
*/
(() => {
"use strict";
const loadLanguagePack = (url, callback) => {
// Install Language Pack.
const getComponentLanguagePack = document.getElementById("component-library-language-pack");
if (!getComponentLanguagePack) {
const componentLanguagePack = document.createElement("script");
componentLanguagePack.setAttribute("src", url);
componentLanguagePack.setAttribute("id", "component-library-language-pack");
componentLanguagePack.addEventListener("load", callback);
document.head.appendChild(componentLanguagePack);
} else {
getComponentLanguagePack.addEventListener("load", callback);
}
};
const initAnimationToggle = () => {
loadLanguagePack("https://services.tmpwebeng.com/component-library/language-pack.js", () => {
// Display which component in use via console:
console.log("%cAnimation Toggle%cv2.9.0", "background: #2d2d2d; color: #fff; padding: 6px 10px; border-radius: 16px 0 0 16px; font-weight: 600;" , "background: #6e00ee; color: #fff; padding: 6px 10px; border-radius: 0 16px 16px 0; font-weight: 600;");
// Animation variables
const animationBody = document.body;
const atAudioDescriptionClassName = "animation-toggle__audio";
const atClass = ".animation-toggle";
const atCallback = document.querySelectorAll(`${atClass}[data-callback]`);
const atCookieName = "AnimationPaused";
const atDescriptionTrackClass = ".animation-toggle__track";
const atEnabledClassName = "animation-enabled";
const atPauseButtonClassName = "animation-toggle__pause";
const atPauseButtonClass = `.${atPauseButtonClassName}`;
const atVideoClass = ".animation-toggle__video";
const atVideoControlsName = "animation-toggle__controls";
const dataAudioDescriptionButton = "data-audio-description-button";
const dataAutoplayDisable = "data-autoplay-disable";
const dataDecorative = "data-decorative";
const dataLoop = "data-loop";
const dataPauseButton = "data-pause-button";
const dataPoster = "data-poster";
const getAnimationWrappers = document.querySelectorAll(atClass);
const getBackgroundVideos = document.querySelectorAll(atVideoClass);
// Language
const atAudioDescriptionLabel = window.atAudioDescriptionLabel;
const atPauseButtonLabel = window.atPauseButtonLabel;
const atVideoLabel = window.atVideoLabel;
// Used to retrieve cookie and pause video(s) if present.
const getCookie = name => {
const match = document.cookie.match(RegExp(`(?:^|;\\s*)${name}=([^;]*)`));
return match ? match[1] : null;
};
// Assign cookie to variable on page load.
let animationPaused = getCookie(atCookieName);
// Used to set and remove cookie.
const setCookie = state => {
document.cookie = `${atCookieName}=${state}; SameSite=None; Secure; path=/`;
};
// If animation disabled in OS settings and cookie is not present then force pause.
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
if(animationPaused === null) {
setCookie("true");
animationPaused = getCookie(atCookieName);
}
}
// For each animation wrapper...
getAnimationWrappers.forEach(wrapper => {
// Create control wrapper.
const btnControls = document.createElement("div");
btnControls.setAttribute("class", atVideoControlsName);
// Prepend control wrapper to atClass.
wrapper.prepend(btnControls);
// Create pause button
const btnPlayPause = document.createElement("button");
// See if wrapper contains custom pause button value; use over default if true.
btnPlayPause.setAttribute("aria-label", wrapper.hasAttribute(dataPauseButton) ? wrapper.getAttribute(dataPauseButton) : atPauseButtonLabel);
// Add class to pause button.
btnPlayPause.classList.add(atPauseButtonClassName);
// Check to see if cookie is false or null.
if(animationPaused === "false" || animationPaused === null) {
btnPlayPause.setAttribute("aria-pressed", "false");
animationBody.classList.add(atEnabledClassName);
} else {
btnPlayPause.setAttribute("aria-pressed", "true");
animationBody.classList.remove(atEnabledClassName);
}
// Append pause button.
btnControls.append(btnPlayPause);
// Pause Toggle Event.
btnPlayPause.addEventListener("click", function() {
const getAtPauseButtonClass = document.querySelectorAll(atPauseButtonClass);
const animationPauseToggles = getAtPauseButtonClass;
if (this.getAttribute("aria-pressed") === "false") {
animationBody.classList.remove(atEnabledClassName);
setCookie("true");
animationPauseToggles.forEach(button => {
button.setAttribute("aria-pressed", "true");
});
} else {
animationBody.classList.add(atEnabledClassName);
setCookie("false");
animationPauseToggles.forEach(button => {
button.setAttribute("aria-pressed", "false");
});
}
getBackgroundVideos.forEach(video => {
if(!video.closest(atClass).querySelector(atPauseButtonClass).hasAttribute("disabled")) {
const isPaused = video.closest(atClass).querySelector(atPauseButtonClass).getAttribute("aria-pressed");
if(isPaused === "false") {
video.play();
} else {
video.pause();
}
}
});
if(this.closest(atClass).hasAttribute("data-callback")) {
const thisButton = this;
const callBackFunction = this.closest(atClass).getAttribute("data-callback");
customCallback(thisButton, callBackFunction);
}
});
if (wrapper.querySelector("track") !== null) {
const btnAudioDescription = document.createElement("button");
btnAudioDescription.setAttribute("aria-label", wrapper.hasAttribute(dataAudioDescriptionButton) ? wrapper.getAttribute(dataAudioDescriptionButton) : atAudioDescriptionLabel);
btnAudioDescription.setAttribute("aria-pressed", "false");
btnAudioDescription.setAttribute("class", atAudioDescriptionClassName);
btnControls.append(btnAudioDescription);
btnAudioDescription.addEventListener("click", function () {
const thisVideo = wrapper.querySelector(atVideoClass);
const thisDescription = wrapper.querySelector(atDescriptionTrackClass);
thisDescription.classList.toggle("active");
const isPressed = this.getAttribute("aria-pressed") === "true";
const track = thisVideo.textTracks ? thisVideo.textTracks[0] : null;
if (!isPressed) {
this.setAttribute("aria-pressed", "true");
if (track) {
track.mode = "hidden";
track.oncuechange = function () {
const currentCue = this.activeCues[0];
if (currentCue) {
thisDescription.innerText = "";
thisDescription.appendChild(currentCue.getCueAsHTML());
const Message = thisDescription.textContent;
const msg = new SpeechSynthesisUtterance(Message);
window.speechSynthesis.speak(msg);
}
};
}
} else {
this.setAttribute("aria-pressed", "false");
if (track) {
track.oncuechange = null;
}
thisDescription.innerText = "";
window.speechSynthesis.cancel();
}
});
}
});
getBackgroundVideos.forEach((video, e) => {
video.setAttribute("crossorigin", "anonymous")
video.setAttribute("disableRemotePlayback", "");
video.setAttribute("disablePictureInPicture", "");
video.id = `animation-toggle-video-${e + 1}`;
video.setAttribute("playsinline" , "");
video.muted = true;
video.load();
if (animationBody.classList.contains(atEnabledClassName)) {
if(!video.hasAttribute(dataAutoplayDisable)) {
video.onloadeddata = () => {
const playPromise = video.play();
if (playPromise !== undefined) {
playPromise.then(() => {
// Automatic playback started!
}).catch(error => {
console.error('Playback error:', error);
});
}
}
}
} else {
if(video.hasAttribute(dataPoster)) {
video.currentTime = video.getAttribute(dataPoster);
}
}
if(video.hasAttribute(dataLoop)) {
let iterations = 1;
video.addEventListener("ended", () => {
if (iterations < video.getAttribute(dataLoop)) {
video.currentTime = 0;
video.play();
iterations ++;
} else {
const toggleButton = video.closest(atClass).querySelector(atPauseButtonClass);
if(toggleButton) {
toggleButton.setAttribute("disabled", "");
}
}
}, false);
} else {
video.setAttribute("loop" , "");
}
if(!video.hasAttribute("aria-label")) {
video.setAttribute("aria-label", atVideoLabel);
}
if(video.hasAttribute(dataDecorative)) {
video.setAttribute("aria-hidden", "true");
video.setAttribute("tabindex", "-1");
video.removeAttribute("aria-label");
}
});
const customCallback = (thisButton, customCallBackName) => {
if (customCallBackName !== null) {
window[customCallBackName](thisButton);
}
};
atCallback.forEach(callback => {
const thisButton = callback.querySelector(atPauseButtonClass);
const callBackFunction = callback.dataset.callback;
customCallback(thisButton, callBackFunction);
});
});
};
initAnimationToggle();
})();<div class="animation-toggle">
<svg aria-hidden="true" class="svg-example" focusable="false" viewBox="0 0 50 38" xmlns="http://www.w3.org/2000/svg">
<path class="svg-example__path svg-example__line-1" d="M0.91,15L0.78,15A1,1,0,0,0,0,16v6a1,1,0,1,0,2,0s0,0,0,0V16a1,1,0,0,0-1-1H0.91Z"/>
<path class="svg-example__path svg-example__line-2" d="M6.91,9L6.78,9A1,1,0,0,0,6,10V28a1,1,0,1,0,2,0s0,0,0,0V10A1,1,0,0,0,7,9H6.91Z"/>
<path class="svg-example__path svg-example__line-3" d="M12.91,0L12.78,0A1,1,0,0,0,12,1V37a1,1,0,1,0,2,0s0,0,0,0V1a1,1,0,0,0-1-1H12.91Z"/>
<path class="svg-example__path svg-example__line-4" d="M18.91,10l-0.12,0A1,1,0,0,0,18,11V27a1,1,0,1,0,2,0s0,0,0,0V11a1,1,0,0,0-1-1H18.91Z"/>
<path class="svg-example__path svg-example__line-5" d="M24.91,15l-0.12,0A1,1,0,0,0,24,16v6a1,1,0,0,0,2,0s0,0,0,0V16a1,1,0,0,0-1-1H24.91Z"/>
<path class="svg-example__path svg-example__line-6" d="M30.91,10l-0.12,0A1,1,0,0,0,30,11V27a1,1,0,1,0,2,0s0,0,0,0V11a1,1,0,0,0-1-1H30.91Z"/>
<path class="svg-example__path svg-example__line-7" d="M36.91,0L36.78,0A1,1,0,0,0,36,1V37a1,1,0,1,0,2,0s0,0,0,0V1a1,1,0,0,0-1-1H36.91Z"/>
<path class="svg-example__path svg-example__line-8" d="M42.91,9L42.78,9A1,1,0,0,0,42,10V28a1,1,0,1,0,2,0s0,0,0,0V10a1,1,0,0,0-1-1H42.91Z"/>
<path class="svg-example__path svg-example__line-9" d="M48.91,15l-0.12,0A1,1,0,0,0,48,16v6a1,1,0,1,0,2,0s0,0,0,0V16a1,1,0,0,0-1-1H48.91Z"/>
</svg>
</div>/*!
Radancy Component Library: Animation Toggle
Contributor(s):
Michael "Spell" Spellacy
Dependencies: Sass
*/
.animation-toggle {
display: grid;
// Control Container (Mandatory)
&__controls {
display: grid;
gap: calc(12rem / 16);
grid-auto-flow: column;
justify-self: end;
margin-block-start: calc(12rem / 16);
margin-inline-end: calc(12rem / 16);
z-index: 2;
}
// Toggle Buttons (Mandatory)
// Important: Be sure to adjust colors as needed so that button stands out on darker or lighter backgrounds.
&__pause, &__audio {
background-color: #000;
background-position: center;
background-repeat: no-repeat;
border-radius: calc(3em / 16);
border: calc(1rem / 16) #fff solid;
cursor: pointer;
height: calc(30rem / 16);
opacity: 0.5;
outline-offset: 0.25em;
transition: all 300ms ease-in-out;
width: calc(30rem / 16);
.animation-toggle:focus-within &, .animation-toggle:hover & {
opacity: 1;
}
}
&__pause {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAMAAAAp4XiDAAAAclBMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////9eWEHEAAAAJXRSTlMA8mB05+Tr4NqonE0f7si8sJWTdXNlXEk6MSTd0qKKf1UnFxEL+BREzAAAAKRJREFUSMftlscOg0AMBR/E9E5CTS/7/78YpA1klbUiwdlzfKM52wAedZjRjE/5DQZdQf5XHuMUE736JcJCZckESB1lUc3FVdkMqBVDqouXz7gIIZe4Okk4F8Dh5oNOXM45fLJfn+wkkUQSSSSRRJLt53XLEQ+5ufn7KsTcPOrkmTGuxJ1ZY3y4MLIHOmsssHC2ZIuJMQrImyE6NTBoczKkVw7AG83JGMcGlBNJAAAAAElFTkSuQmCC");
background-size: 0.625rem;
&[aria-pressed="true"] {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOoAAAE2AQMAAABV0ZztAAAABlBMVEUAAAD///+l2Z/dAAAAAXRSTlMAQObYZgAAAipJREFUaN7t2rFNBjEMhuFDFJSM8I/CaHg0j+IRXLqw/CEQBc29KSyoSPtId7kUucT+rutZccF4qInfJAOW6OlPkuqenyV6+Ysk3fOrRHN7fLIzJ322VMxzy++frAMbcwDT1L9UxTzMOrAxB3MyN/Mw68DOHMzJ3MzDrAM7czAXczMPs4zZmYO5mJt5mGXMzpzMxdzMOrAxO3MyF/Mw68DGHMzJXMzDrAMbczAnczMPsw7szMGczM08zDqwMwdzMTfzMMuYnTmYi7mZdWBjduZkLuZm1oGN2ZmTuZiHWQc25mBO5mIeZh3YmIM5mZt5mHVgZw7mZG7mYZYxO3MwF3MzD7OM2ZmDuZibWQc2ZmdO5mJuZh3YmJ05mYt5mHVgYw7mYm7mYZYxO3N8881I5mLuFQ+zdmy/yf7Pf77m14aHuVdczMkczM5szKutp5iT2Zlts2E3czEHs2/+gsNczMkczMa8OrcUczA7syHP6qyYzMFszKsDeDEnszMb8qwuPckczMa8uioWczI7s23u381czMHsm6rFMBdzMjuzMa/qTMUczM68qs41czIHszGvCqbFHMzObMizKlInczAb86q0X8zJ7My26Zc0czIHs2+6TMNczMnszIY8q75gMQezM6+6qcWczMFszKsGd1FiQQpmZ76Qh7mZkzkoBiIZ84Xcd/z4uaR3nMyO4RkZ8nAyp5kTYz8KDA3JmDmR1JxnSuCH5MAvGtDrSXGPH9boP57gj/lMAAAAAElFTkSuQmCC")
}
&[disabled] {
background-color: #333;
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAMAAAAp4XiDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDkuMC1jMDAwIDc5LjE3MWMyN2ZhYiwgMjAyMi8wOC8xNi0yMjozNTo0MSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIDI0LjAgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjFGNDQzOTFBMDNFNzExRUZBREE3RDY3NEY5MTNEMDlEIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjFGNDQzOTFCMDNFNzExRUZBREE3RDY3NEY5MTNEMDlEIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6MUY0NDM5MTgwM0U3MTFFRkFEQTdENjc0RjkxM0QwOUQiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6MUY0NDM5MTkwM0U3MTFFRkFEQTdENjc0RjkxM0QwOUQiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7n3NecAAAABlBMVEX///////9VfPVsAAAAAnRSTlP/AOW3MEoAAADVSURBVHjarNbbDsMgDAPQ5P9/epo2qQEnthetj4WzUciFSHjieJpxOr9VIQGgcMSJwgKHCVMUE654TPSCvMxmcN71SvjRXYMhBQwHXxagL1FBVad8iAgpMA3h5k2emd4eFHKteP6bg1AzLcYxEEBWFFxxOhpNpBmCwyOzISfupA6UHfWa/p5NUnyk2J35YLTRSTLHkhNTkDGmGfKFFhYkwtRETrPqtRUmvVS0q8RZLX8xWJPTLeOLZrFpSVP1Z41v0143TXxzVeDmj9eezeXKusK9BBgAWdwE4I4jwMQAAAAASUVORK5CYII=");
cursor: default;
}
[data-pause-remove] & {
display: none;
}
}
&__audio {
background-image: url("data:image/png;base64,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");
background-size: calc(18rem / 16);
}
// Video (Optional)
&__video {
width: 100%;
}
// Backdrop (Optional)
&__backdrop {
&:not(.animation-enabled &) {
&::after {
background-color: rgba(0, 0, 0, 0.5);
content: "";
pointer-events: none;
position: relative;
z-index: 1;
}
}
}
// Primary Content (Optional)
&__content {
color: #fff;
font-size: clamp(1rem, 9vw, 6rem);
font-weight: bold;
line-height: normal;
place-self: center;
text-align: center;
z-index: 2;
}
// Audio Description Track (Optional)
&__track {
align-self: end;
background-color: rgba(0,0,0,.8);
border-radius: 0.3125em;
color: #fff;
display: none;
font-size: 0.9rem;
line-height: normal;
margin: 1em;
padding: 0.7em;
text-align: center;
z-index: 1;
@media(min-width: 42.5em) {
font-size: 1.1rem;
justify-self: center;
}
&.active {
display: block;
&:empty {
display: none;
}
}
}
// Shared
&__controls, &__video, &__content, &__track, &__backdrop::after {
grid-area: 1 / 1;
}
}
// Example: SVG Animation
.svg-example {
grid-area: 1 / 1;
justify-self: center;
max-width: calc(400rem / 16);
&__path {
fill: #6F00EF;
transform-origin: center;
transform: scale(1, 0.5);
animation: pulse 1s infinite forwards;
animation-play-state: paused;
.animation-enabled & {
animation-play-state: running;
}
}
&__line-1 {
animation-delay: 0.15s;
}
&__line-2 {
animation-delay: 0.3s;
}
&__line-3 {
animation-delay: 0.45s;
}
&__line-4 {
animation-delay: 0.6s;
}
&__line-5 {
animation-delay: 0.75s;
}
&__line-6 {
animation-delay: 0.9s;
}
&__line-7 {
animation-delay: 1.05s;
}
&__line-8 {
animation-delay: 1.2s;
}
&__line-9 {
animation-delay: 1.35s;
}
}
@keyframes pulse {
0% {
transform: scale(1, 0.5)
}
50% {
transform: scale(1, 1)
}
100% {
transform: scale(1, 0.5)
}
}/*!
Radancy Component Library: Animation Toggle
Contributor(s):
Michael "Spell" Spellacy
Dependencies: None
*/
(() => {
"use strict";
const loadLanguagePack = (url, callback) => {
// Install Language Pack.
const getComponentLanguagePack = document.getElementById("component-library-language-pack");
if (!getComponentLanguagePack) {
const componentLanguagePack = document.createElement("script");
componentLanguagePack.setAttribute("src", url);
componentLanguagePack.setAttribute("id", "component-library-language-pack");
componentLanguagePack.addEventListener("load", callback);
document.head.appendChild(componentLanguagePack);
} else {
getComponentLanguagePack.addEventListener("load", callback);
}
};
const initAnimationToggle = () => {
loadLanguagePack("https://services.tmpwebeng.com/component-library/language-pack.js", () => {
// Display which component in use via console:
console.log("%cAnimation Toggle%cv2.9.0", "background: #2d2d2d; color: #fff; padding: 6px 10px; border-radius: 16px 0 0 16px; font-weight: 600;" , "background: #6e00ee; color: #fff; padding: 6px 10px; border-radius: 0 16px 16px 0; font-weight: 600;");
// Animation variables
const animationBody = document.body;
const atAudioDescriptionClassName = "animation-toggle__audio";
const atClass = ".animation-toggle";
const atCallback = document.querySelectorAll(`${atClass}[data-callback]`);
const atCookieName = "AnimationPaused";
const atDescriptionTrackClass = ".animation-toggle__track";
const atEnabledClassName = "animation-enabled";
const atPauseButtonClassName = "animation-toggle__pause";
const atPauseButtonClass = `.${atPauseButtonClassName}`;
const atVideoClass = ".animation-toggle__video";
const atVideoControlsName = "animation-toggle__controls";
const dataAudioDescriptionButton = "data-audio-description-button";
const dataAutoplayDisable = "data-autoplay-disable";
const dataDecorative = "data-decorative";
const dataLoop = "data-loop";
const dataPauseButton = "data-pause-button";
const dataPoster = "data-poster";
const getAnimationWrappers = document.querySelectorAll(atClass);
const getBackgroundVideos = document.querySelectorAll(atVideoClass);
// Language
const atAudioDescriptionLabel = window.atAudioDescriptionLabel;
const atPauseButtonLabel = window.atPauseButtonLabel;
const atVideoLabel = window.atVideoLabel;
// Used to retrieve cookie and pause video(s) if present.
const getCookie = name => {
const match = document.cookie.match(RegExp(`(?:^|;\\s*)${name}=([^;]*)`));
return match ? match[1] : null;
};
// Assign cookie to variable on page load.
let animationPaused = getCookie(atCookieName);
// Used to set and remove cookie.
const setCookie = state => {
document.cookie = `${atCookieName}=${state}; SameSite=None; Secure; path=/`;
};
// If animation disabled in OS settings and cookie is not present then force pause.
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
if(animationPaused === null) {
setCookie("true");
animationPaused = getCookie(atCookieName);
}
}
// For each animation wrapper...
getAnimationWrappers.forEach(wrapper => {
// Create control wrapper.
const btnControls = document.createElement("div");
btnControls.setAttribute("class", atVideoControlsName);
// Prepend control wrapper to atClass.
wrapper.prepend(btnControls);
// Create pause button
const btnPlayPause = document.createElement("button");
// See if wrapper contains custom pause button value; use over default if true.
btnPlayPause.setAttribute("aria-label", wrapper.hasAttribute(dataPauseButton) ? wrapper.getAttribute(dataPauseButton) : atPauseButtonLabel);
// Add class to pause button.
btnPlayPause.classList.add(atPauseButtonClassName);
// Check to see if cookie is false or null.
if(animationPaused === "false" || animationPaused === null) {
btnPlayPause.setAttribute("aria-pressed", "false");
animationBody.classList.add(atEnabledClassName);
} else {
btnPlayPause.setAttribute("aria-pressed", "true");
animationBody.classList.remove(atEnabledClassName);
}
// Append pause button.
btnControls.append(btnPlayPause);
// Pause Toggle Event.
btnPlayPause.addEventListener("click", function() {
const getAtPauseButtonClass = document.querySelectorAll(atPauseButtonClass);
const animationPauseToggles = getAtPauseButtonClass;
if (this.getAttribute("aria-pressed") === "false") {
animationBody.classList.remove(atEnabledClassName);
setCookie("true");
animationPauseToggles.forEach(button => {
button.setAttribute("aria-pressed", "true");
});
} else {
animationBody.classList.add(atEnabledClassName);
setCookie("false");
animationPauseToggles.forEach(button => {
button.setAttribute("aria-pressed", "false");
});
}
getBackgroundVideos.forEach(video => {
if(!video.closest(atClass).querySelector(atPauseButtonClass).hasAttribute("disabled")) {
const isPaused = video.closest(atClass).querySelector(atPauseButtonClass).getAttribute("aria-pressed");
if(isPaused === "false") {
video.play();
} else {
video.pause();
}
}
});
if(this.closest(atClass).hasAttribute("data-callback")) {
const thisButton = this;
const callBackFunction = this.closest(atClass).getAttribute("data-callback");
customCallback(thisButton, callBackFunction);
}
});
if (wrapper.querySelector("track") !== null) {
const btnAudioDescription = document.createElement("button");
btnAudioDescription.setAttribute("aria-label", wrapper.hasAttribute(dataAudioDescriptionButton) ? wrapper.getAttribute(dataAudioDescriptionButton) : atAudioDescriptionLabel);
btnAudioDescription.setAttribute("aria-pressed", "false");
btnAudioDescription.setAttribute("class", atAudioDescriptionClassName);
btnControls.append(btnAudioDescription);
btnAudioDescription.addEventListener("click", function () {
const thisVideo = wrapper.querySelector(atVideoClass);
const thisDescription = wrapper.querySelector(atDescriptionTrackClass);
thisDescription.classList.toggle("active");
const isPressed = this.getAttribute("aria-pressed") === "true";
const track = thisVideo.textTracks ? thisVideo.textTracks[0] : null;
if (!isPressed) {
this.setAttribute("aria-pressed", "true");
if (track) {
track.mode = "hidden";
track.oncuechange = function () {
const currentCue = this.activeCues[0];
if (currentCue) {
thisDescription.innerText = "";
thisDescription.appendChild(currentCue.getCueAsHTML());
const Message = thisDescription.textContent;
const msg = new SpeechSynthesisUtterance(Message);
window.speechSynthesis.speak(msg);
}
};
}
} else {
this.setAttribute("aria-pressed", "false");
if (track) {
track.oncuechange = null;
}
thisDescription.innerText = "";
window.speechSynthesis.cancel();
}
});
}
});
getBackgroundVideos.forEach((video, e) => {
video.setAttribute("crossorigin", "anonymous")
video.setAttribute("disableRemotePlayback", "");
video.setAttribute("disablePictureInPicture", "");
video.id = `animation-toggle-video-${e + 1}`;
video.setAttribute("playsinline" , "");
video.muted = true;
video.load();
if (animationBody.classList.contains(atEnabledClassName)) {
if(!video.hasAttribute(dataAutoplayDisable)) {
video.onloadeddata = () => {
const playPromise = video.play();
if (playPromise !== undefined) {
playPromise.then(() => {
// Automatic playback started!
}).catch(error => {
console.error('Playback error:', error);
});
}
}
}
} else {
if(video.hasAttribute(dataPoster)) {
video.currentTime = video.getAttribute(dataPoster);
}
}
if(video.hasAttribute(dataLoop)) {
let iterations = 1;
video.addEventListener("ended", () => {
if (iterations < video.getAttribute(dataLoop)) {
video.currentTime = 0;
video.play();
iterations ++;
} else {
const toggleButton = video.closest(atClass).querySelector(atPauseButtonClass);
if(toggleButton) {
toggleButton.setAttribute("disabled", "");
}
}
}, false);
} else {
video.setAttribute("loop" , "");
}
if(!video.hasAttribute("aria-label")) {
video.setAttribute("aria-label", atVideoLabel);
}
if(video.hasAttribute(dataDecorative)) {
video.setAttribute("aria-hidden", "true");
video.setAttribute("tabindex", "-1");
video.removeAttribute("aria-label");
}
});
const customCallback = (thisButton, customCallBackName) => {
if (customCallBackName !== null) {
window[customCallBackName](thisButton);
}
};
atCallback.forEach(callback => {
const thisButton = callback.querySelector(atPauseButtonClass);
const callBackFunction = callback.dataset.callback;
customCallback(thisButton, callBackFunction);
});
});
};
initAnimationToggle();
})();Note: This video utilizes an Intersection Observer triggered by a callback function. When positioned at the top of the page, it loads instantly; when lower, it plays as you scroll to it. This demo will only play once, requiring a page refresh for replay.
<div class="transparent-video-example">
<div class="animation-toggle section-animation" data-callback="scrollObserver" data-pause-remove>
<video class="animation-toggle__video section-animation__video" data-autoplay-disable data-decorative data-loop="1" data-poster="1.8" loading="lazy">
<source media="(min-width: 46.5em)" src="https://radancy.dev/component-library/animation-toggle/demo-hvc1-lg.mp4" type='video/mp4; codecs="hvc1"'>
<source media="(min-width: 46.5em)" src="https://radancy.dev/component-library/animation-toggle/demo-webm-lg.webm" type="video/webm">
<source src="https://radancy.dev/component-library/animation-toggle/demo-hvc1-sm.mp4" type='video/mp4; codecs="hvc1"'>
<source src="https://radancy.dev/component-library/animation-toggle/demo-webm-sm.webm" type="video/webm">
</video>
</div>
</div>/*!
Radancy Component Library: Animation Toggle
Contributor(s):
Michael "Spell" Spellacy
Dependencies: Sass
*/
.animation-toggle {
display: grid;
// Control Container (Mandatory)
&__controls {
display: grid;
gap: calc(12rem / 16);
grid-auto-flow: column;
justify-self: end;
margin-block-start: calc(12rem / 16);
margin-inline-end: calc(12rem / 16);
z-index: 2;
}
// Toggle Buttons (Mandatory)
// Important: Be sure to adjust colors as needed so that button stands out on darker or lighter backgrounds.
&__pause, &__audio {
background-color: #000;
background-position: center;
background-repeat: no-repeat;
border-radius: calc(3em / 16);
border: calc(1rem / 16) #fff solid;
cursor: pointer;
height: calc(30rem / 16);
opacity: 0.5;
outline-offset: 0.25em;
transition: all 300ms ease-in-out;
width: calc(30rem / 16);
.animation-toggle:focus-within &, .animation-toggle:hover & {
opacity: 1;
}
}
&__pause {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAMAAAAp4XiDAAAAclBMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////9eWEHEAAAAJXRSTlMA8mB05+Tr4NqonE0f7si8sJWTdXNlXEk6MSTd0qKKf1UnFxEL+BREzAAAAKRJREFUSMftlscOg0AMBR/E9E5CTS/7/78YpA1klbUiwdlzfKM52wAedZjRjE/5DQZdQf5XHuMUE736JcJCZckESB1lUc3FVdkMqBVDqouXz7gIIZe4Okk4F8Dh5oNOXM45fLJfn+wkkUQSSSSRRJLt53XLEQ+5ufn7KsTcPOrkmTGuxJ1ZY3y4MLIHOmsssHC2ZIuJMQrImyE6NTBoczKkVw7AG83JGMcGlBNJAAAAAElFTkSuQmCC");
background-size: 0.625rem;
&[aria-pressed="true"] {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOoAAAE2AQMAAABV0ZztAAAABlBMVEUAAAD///+l2Z/dAAAAAXRSTlMAQObYZgAAAipJREFUaN7t2rFNBjEMhuFDFJSM8I/CaHg0j+IRXLqw/CEQBc29KSyoSPtId7kUucT+rutZccF4qInfJAOW6OlPkuqenyV6+Ysk3fOrRHN7fLIzJ322VMxzy++frAMbcwDT1L9UxTzMOrAxB3MyN/Mw68DOHMzJ3MzDrAM7czAXczMPs4zZmYO5mJt5mGXMzpzMxdzMOrAxO3MyF/Mw68DGHMzJXMzDrAMbczAnczMPsw7szMGczM08zDqwMwdzMTfzMMuYnTmYi7mZdWBjduZkLuZm1oGN2ZmTuZiHWQc25mBO5mIeZh3YmIM5mZt5mHVgZw7mZG7mYZYxO3MwF3MzD7OM2ZmDuZibWQc2ZmdO5mJuZh3YmJ05mYt5mHVgYw7mYm7mYZYxO3N8881I5mLuFQ+zdmy/yf7Pf77m14aHuVdczMkczM5szKutp5iT2Zlts2E3czEHs2/+gsNczMkczMa8OrcUczA7syHP6qyYzMFszKsDeDEnszMb8qwuPckczMa8uioWczI7s23u381czMHsm6rFMBdzMjuzMa/qTMUczM68qs41czIHszGvCqbFHMzObMizKlInczAb86q0X8zJ7My26Zc0czIHs2+6TMNczMnszIY8q75gMQezM6+6qcWczMFszKsGd1FiQQpmZ76Qh7mZkzkoBiIZ84Xcd/z4uaR3nMyO4RkZ8nAyp5kTYz8KDA3JmDmR1JxnSuCH5MAvGtDrSXGPH9boP57gj/lMAAAAAElFTkSuQmCC")
}
&[disabled] {
background-color: #333;
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAMAAAAp4XiDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDkuMC1jMDAwIDc5LjE3MWMyN2ZhYiwgMjAyMi8wOC8xNi0yMjozNTo0MSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIDI0LjAgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjFGNDQzOTFBMDNFNzExRUZBREE3RDY3NEY5MTNEMDlEIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjFGNDQzOTFCMDNFNzExRUZBREE3RDY3NEY5MTNEMDlEIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6MUY0NDM5MTgwM0U3MTFFRkFEQTdENjc0RjkxM0QwOUQiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6MUY0NDM5MTkwM0U3MTFFRkFEQTdENjc0RjkxM0QwOUQiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7n3NecAAAABlBMVEX///////9VfPVsAAAAAnRSTlP/AOW3MEoAAADVSURBVHjarNbbDsMgDAPQ5P9/epo2qQEnthetj4WzUciFSHjieJpxOr9VIQGgcMSJwgKHCVMUE654TPSCvMxmcN71SvjRXYMhBQwHXxagL1FBVad8iAgpMA3h5k2emd4eFHKteP6bg1AzLcYxEEBWFFxxOhpNpBmCwyOzISfupA6UHfWa/p5NUnyk2J35YLTRSTLHkhNTkDGmGfKFFhYkwtRETrPqtRUmvVS0q8RZLX8xWJPTLeOLZrFpSVP1Z41v0143TXxzVeDmj9eezeXKusK9BBgAWdwE4I4jwMQAAAAASUVORK5CYII=");
cursor: default;
}
[data-pause-remove] & {
display: none;
}
}
&__audio {
background-image: url("data:image/png;base64,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");
background-size: calc(18rem / 16);
}
// Video (Optional)
&__video {
width: 100%;
}
// Backdrop (Optional)
&__backdrop {
&:not(.animation-enabled &) {
&::after {
background-color: rgba(0, 0, 0, 0.5);
content: "";
pointer-events: none;
position: relative;
z-index: 1;
}
}
}
// Primary Content (Optional)
&__content {
color: #fff;
font-size: clamp(1rem, 9vw, 6rem);
font-weight: bold;
line-height: normal;
place-self: center;
text-align: center;
z-index: 2;
}
// Audio Description Track (Optional)
&__track {
align-self: end;
background-color: rgba(0,0,0,.8);
border-radius: 0.3125em;
color: #fff;
display: none;
font-size: 0.9rem;
line-height: normal;
margin: 1em;
padding: 0.7em;
text-align: center;
z-index: 1;
@media(min-width: 42.5em) {
font-size: 1.1rem;
justify-self: center;
}
&.active {
display: block;
&:empty {
display: none;
}
}
}
// Shared
&__controls, &__video, &__content, &__track, &__backdrop::after {
grid-area: 1 / 1;
}
}
// Example: Transparent Video
.transparent-video-example {
position: relative;
overflow: hidden;
// Not Required
&::before, &::after {
content: "";
display: block;
height: calc(800em/16);
inset-block-end: 0;
position: absolute;
width: 33.3%;
}
&:before {
background-color: orange;
inset-inline-start: 0;
}
&:after {
background-color: #6F00EF;
inset-inline-end: 0;
}
}
.section-animation {
// Not Required
&:before {
background-color: tomato;
content: "";
height: calc(150rem/16);
inset-block-start: 20%;
inset-inline-start: 20%;
opacity: 0.9;
position: absolute;
width: 20%;
z-index: 2;
}
// Required
&__video {
max-height: 400px;
max-width: 1920px;
pointer-events: none; // Required so that elements behind video object can be selected and pressed.
z-index: 1;
}
}/*!
Radancy Component Library: Animation Toggle
Contributor(s):
Michael "Spell" Spellacy
Dependencies: None
*/
(() => {
"use strict";
const loadLanguagePack = (url, callback) => {
// Install Language Pack.
const getComponentLanguagePack = document.getElementById("component-library-language-pack");
if (!getComponentLanguagePack) {
const componentLanguagePack = document.createElement("script");
componentLanguagePack.setAttribute("src", url);
componentLanguagePack.setAttribute("id", "component-library-language-pack");
componentLanguagePack.addEventListener("load", callback);
document.head.appendChild(componentLanguagePack);
} else {
getComponentLanguagePack.addEventListener("load", callback);
}
};
const initAnimationToggle = () => {
loadLanguagePack("https://services.tmpwebeng.com/component-library/language-pack.js", () => {
// Display which component in use via console:
console.log("%cAnimation Toggle%cv2.9.0", "background: #2d2d2d; color: #fff; padding: 6px 10px; border-radius: 16px 0 0 16px; font-weight: 600;" , "background: #6e00ee; color: #fff; padding: 6px 10px; border-radius: 0 16px 16px 0; font-weight: 600;");
// Animation variables
const animationBody = document.body;
const atAudioDescriptionClassName = "animation-toggle__audio";
const atClass = ".animation-toggle";
const atCallback = document.querySelectorAll(`${atClass}[data-callback]`);
const atCookieName = "AnimationPaused";
const atDescriptionTrackClass = ".animation-toggle__track";
const atEnabledClassName = "animation-enabled";
const atPauseButtonClassName = "animation-toggle__pause";
const atPauseButtonClass = `.${atPauseButtonClassName}`;
const atVideoClass = ".animation-toggle__video";
const atVideoControlsName = "animation-toggle__controls";
const dataAudioDescriptionButton = "data-audio-description-button";
const dataAutoplayDisable = "data-autoplay-disable";
const dataDecorative = "data-decorative";
const dataLoop = "data-loop";
const dataPauseButton = "data-pause-button";
const dataPoster = "data-poster";
const getAnimationWrappers = document.querySelectorAll(atClass);
const getBackgroundVideos = document.querySelectorAll(atVideoClass);
// Language
const atAudioDescriptionLabel = window.atAudioDescriptionLabel;
const atPauseButtonLabel = window.atPauseButtonLabel;
const atVideoLabel = window.atVideoLabel;
// Used to retrieve cookie and pause video(s) if present.
const getCookie = name => {
const match = document.cookie.match(RegExp(`(?:^|;\\s*)${name}=([^;]*)`));
return match ? match[1] : null;
};
// Assign cookie to variable on page load.
let animationPaused = getCookie(atCookieName);
// Used to set and remove cookie.
const setCookie = state => {
document.cookie = `${atCookieName}=${state}; SameSite=None; Secure; path=/`;
};
// If animation disabled in OS settings and cookie is not present then force pause.
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
if(animationPaused === null) {
setCookie("true");
animationPaused = getCookie(atCookieName);
}
}
// For each animation wrapper...
getAnimationWrappers.forEach(wrapper => {
// Create control wrapper.
const btnControls = document.createElement("div");
btnControls.setAttribute("class", atVideoControlsName);
// Prepend control wrapper to atClass.
wrapper.prepend(btnControls);
// Create pause button
const btnPlayPause = document.createElement("button");
// See if wrapper contains custom pause button value; use over default if true.
btnPlayPause.setAttribute("aria-label", wrapper.hasAttribute(dataPauseButton) ? wrapper.getAttribute(dataPauseButton) : atPauseButtonLabel);
// Add class to pause button.
btnPlayPause.classList.add(atPauseButtonClassName);
// Check to see if cookie is false or null.
if(animationPaused === "false" || animationPaused === null) {
btnPlayPause.setAttribute("aria-pressed", "false");
animationBody.classList.add(atEnabledClassName);
} else {
btnPlayPause.setAttribute("aria-pressed", "true");
animationBody.classList.remove(atEnabledClassName);
}
// Append pause button.
btnControls.append(btnPlayPause);
// Pause Toggle Event.
btnPlayPause.addEventListener("click", function() {
const getAtPauseButtonClass = document.querySelectorAll(atPauseButtonClass);
const animationPauseToggles = getAtPauseButtonClass;
if (this.getAttribute("aria-pressed") === "false") {
animationBody.classList.remove(atEnabledClassName);
setCookie("true");
animationPauseToggles.forEach(button => {
button.setAttribute("aria-pressed", "true");
});
} else {
animationBody.classList.add(atEnabledClassName);
setCookie("false");
animationPauseToggles.forEach(button => {
button.setAttribute("aria-pressed", "false");
});
}
getBackgroundVideos.forEach(video => {
if(!video.closest(atClass).querySelector(atPauseButtonClass).hasAttribute("disabled")) {
const isPaused = video.closest(atClass).querySelector(atPauseButtonClass).getAttribute("aria-pressed");
if(isPaused === "false") {
video.play();
} else {
video.pause();
}
}
});
if(this.closest(atClass).hasAttribute("data-callback")) {
const thisButton = this;
const callBackFunction = this.closest(atClass).getAttribute("data-callback");
customCallback(thisButton, callBackFunction);
}
});
if (wrapper.querySelector("track") !== null) {
const btnAudioDescription = document.createElement("button");
btnAudioDescription.setAttribute("aria-label", wrapper.hasAttribute(dataAudioDescriptionButton) ? wrapper.getAttribute(dataAudioDescriptionButton) : atAudioDescriptionLabel);
btnAudioDescription.setAttribute("aria-pressed", "false");
btnAudioDescription.setAttribute("class", atAudioDescriptionClassName);
btnControls.append(btnAudioDescription);
btnAudioDescription.addEventListener("click", function () {
const thisVideo = wrapper.querySelector(atVideoClass);
const thisDescription = wrapper.querySelector(atDescriptionTrackClass);
thisDescription.classList.toggle("active");
const isPressed = this.getAttribute("aria-pressed") === "true";
const track = thisVideo.textTracks ? thisVideo.textTracks[0] : null;
if (!isPressed) {
this.setAttribute("aria-pressed", "true");
if (track) {
track.mode = "hidden";
track.oncuechange = function () {
const currentCue = this.activeCues[0];
if (currentCue) {
thisDescription.innerText = "";
thisDescription.appendChild(currentCue.getCueAsHTML());
const Message = thisDescription.textContent;
const msg = new SpeechSynthesisUtterance(Message);
window.speechSynthesis.speak(msg);
}
};
}
} else {
this.setAttribute("aria-pressed", "false");
if (track) {
track.oncuechange = null;
}
thisDescription.innerText = "";
window.speechSynthesis.cancel();
}
});
}
});
getBackgroundVideos.forEach((video, e) => {
video.setAttribute("crossorigin", "anonymous")
video.setAttribute("disableRemotePlayback", "");
video.setAttribute("disablePictureInPicture", "");
video.id = `animation-toggle-video-${e + 1}`;
video.setAttribute("playsinline" , "");
video.muted = true;
video.load();
if (animationBody.classList.contains(atEnabledClassName)) {
if(!video.hasAttribute(dataAutoplayDisable)) {
video.onloadeddata = () => {
const playPromise = video.play();
if (playPromise !== undefined) {
playPromise.then(() => {
// Automatic playback started!
}).catch(error => {
console.error('Playback error:', error);
});
}
}
}
} else {
if(video.hasAttribute(dataPoster)) {
video.currentTime = video.getAttribute(dataPoster);
}
}
if(video.hasAttribute(dataLoop)) {
let iterations = 1;
video.addEventListener("ended", () => {
if (iterations < video.getAttribute(dataLoop)) {
video.currentTime = 0;
video.play();
iterations ++;
} else {
const toggleButton = video.closest(atClass).querySelector(atPauseButtonClass);
if(toggleButton) {
toggleButton.setAttribute("disabled", "");
}
}
}, false);
} else {
video.setAttribute("loop" , "");
}
if(!video.hasAttribute("aria-label")) {
video.setAttribute("aria-label", atVideoLabel);
}
if(video.hasAttribute(dataDecorative)) {
video.setAttribute("aria-hidden", "true");
video.setAttribute("tabindex", "-1");
video.removeAttribute("aria-label");
}
});
const customCallback = (thisButton, customCallBackName) => {
if (customCallBackName !== null) {
window[customCallBackName](thisButton);
}
};
atCallback.forEach(callback => {
const thisButton = callback.querySelector(atPauseButtonClass);
const callBackFunction = callback.dataset.callback;
customCallback(thisButton, callBackFunction);
});
});
};
initAnimationToggle();
})();// Callback: Transparent Video (Intersection Observer)
// Learn More: https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
function scrollObserver() {
const sectionAnimation = document.querySelectorAll(".section-animation");
const sectionAnimationVideoClass = ".section-animation__video";
const sectionAnimationVideoScroll = (sections, observer) => {
sections.forEach((section) => {
if (section.isIntersecting) {
// Target element is now in view
const sectionTarget = section.target;
const selectVideo = sectionTarget.querySelector(sectionAnimationVideoClass);
const animationEnabled = document.body.classList.contains("animation-enabled");
if (animationEnabled) {
selectVideo.play();
} else {
selectVideo.pause();
}
// Stop observing once the element is in view (if needed)
observer.unobserve(sectionTarget);
}
});
}
// Create an Intersection Observer instance
const observer = new IntersectionObserver(sectionAnimationVideoScroll, {
threshold: 1.0
});
// Start observing each target element
sectionAnimation.forEach((element) => {
observer.observe(element);
});
}Transparent Video? Anchor (Transparent Video?)
It can be done! Check out our blog post, Transparent Video for the Web, to see how this was achieved.
Responsible Animation Anchor (Responsible Animation)
Consider the impact that animation can have on people with disabilities. For some, animation can be a distraction; for others, it can cause discomfort or illness. In severe cases, it can pose serious danger, like triggering seizures in individuals with photosensitivity. By following these guidelines, we can ensure that our digital experiences are both enjoyable and safe for everyone!
- Any web animation that loops must include a pause button. There are no exceptions here. In applications where this cannot be controlled, such as email, animation should be avoided.
- Animations that occur once when interacting with a web page are exempt from having a pause button if they do not last for more than 5 seconds and are not excessive in their motion. However, developers should always exercise caution and honor user settings by including
prefers-reduced-motionto help reduce or stop animation where needed. Please visit the Mozilla Developer Network for more details about this useful media query. - Animations that flash more than 3 times per second are not allowed on any websites or applications, as they may induce seizures. A pause button will be ineffective here. If you suspect that an animation may be a risk, please bring this to the attention of the Accessibility Team.
Video Performance Anchor (Video Performance)
Be mindful of the negative impact background videos can have on page performance and consider these best practices when producing them:
- Ensure that video is delivered in an optimized
webmand/ormp4format. Keeping file sizes as small as possible, without compromising quality, is critical. - If video is delivered larger than the containing element it will exist within on the page, this is an opportunity to optimize. For example, if video is 1920 pixels wide but will only be shown within a maximum width of 1440 pixels, reducing the width and height of the video will help reduce overall video weight.
- Aim to keep micro or looping videos between 15-30 seconds in length. The shorter the video, the less of an impact it will have on overall page performance.
Properties Anchor (Properties)
Use the following properties to configure this component.
| ARIA | Applies To | Note | Description |
|---|---|---|---|
aria-label |
video |
String, Optional | Used to pass custom label to video element. This will override the default label, which is "Background Animation". |
aria-live |
.animation-toggle__track |
Conditional | Required on .animation-toggle__track when present and must contain a value of "polite". |
| Attribute | Applies To | Note | Description |
|---|---|---|---|
data-audio-description-button |
.animation-toggle |
String, Optional | Used to pass custom label to .animation-toggle__audio. This will override the default label, which is "Audio Description". |
data-autoplay-disable |
video |
Boolean, Optional | Used to prevent the video from autoplaying on page load. This may be useful when used with data-callback, where you may wish for the video to only load, for example, using an intersection observer. |
data-callback |
.animation-toggle |
String, Optional | Used to define a custom callback function to initiate when animation first loads and pause button is accessed. Particularly useful for handling third-party scripts. The callback accepts a single parameter called thisButton that you can use to target the button itself, if needed. |
data-decorative |
video |
Boolean, Optional | Used to define a purely decorative video, which will not be accessible to assistive technology or keyboard users. Often, this should be used with data-pause-remove. |
data-loop |
video |
Number, Optional | Used to set the amount of times a video will loop. Please note that when a loop iteration is completed, a video cannot be restarted and the pause button will become disabled. |
data-pause-button |
.animation-toggle |
String, Optional | Used to pass custom label to .animation-toggle__pause. This will override the default label, which is "Pause Animation". |
data-pause-remove |
.animation-toggle |
Boolean, Optional | Used to remove a pause button from animation. This attribute should only be used in certain configurations. For example, short, non-looping animations. |
data-poster |
video |
Number, Optional | Used to indicate at which point video will load when paused. Accepts any valid time increment. Video will start from this point when play is enabled. |
| Class | Applies To | Note | Description |
|---|---|---|---|
.animation-enabled |
body |
Component | Applied on page load. Page animations will only run when this class is present. Removing it pauses animation. If you are leveraging this class in your CSS or scripting, then there is no need to use prefers-reduced-motion. |
.animation-toggle |
div, section |
Mandatory | Required on the parent element that wraps your animation and content. |
.animation-toggle__audio |
button |
Component | Used on all audio description buttons. |
.animation-toggle__backdrop |
.animation-toggle |
Optional | Append to .animation-toggle class. This will allow video to animate in on page load and for background shading to display when video is paused. Often used on hero banner and meant to improve contrast of foreground text. |
.animation-toggle__controls |
div |
Component | Used to contain pause and audio description buttons. |
.animation-toggle__pause |
button |
Component | Used on all pause buttons. This may never be hidden and is an integral part of the component. |
.animation-toggle__track |
div |
Optional | Used on single div element that is a child of .animation-toggle. The aria-live attribute is required on this element when used. |
.animation-toggle__video |
video |
Mandatory | Required on video element that is a child of .animation-toggle. Multiple videos may be used if desired. |
| Name | Value | Type | Secure | SameSite | Domain | Path |
|---|---|---|---|---|---|---|
AnimationPaused |
true/false | Persistent | true | None | N/A | / |
Release Notes Anchor (Release Notes)
Keep up to date on major changes to this component.
- Feature
- April 07, 2026: Added support for
loading=lazyto video for better performance support. - Note
- April 24, 2025: Upgraded component to ES6 and fixed a small z-index bug in the Sass.
- Behavior
- Pausing one animation will result in a pause for all site-wide animations utilizing this script. Any animations that are paused will maintain their paused state when user returns to the site.
- Behavior
- The script utilizes
prefers-reduced-motion. If a user disables system animations, all animations will honor this setting. - Behavior
- When no cookie is set and reduce animation is enabled, the animation stops.
- Behavior
- When no cookie is set and reduce animation is not enabled, the animation plays.
- Behavior
- When cookie is set to false (not paused) and reduce animation is enabled, OS setting take precedent and cookie is set to true (paused).
- Behavior
- When reduce animation is not enabled, cookie settings take precedent.
- Roadmap
- When reduce animation is enabled, it would be great if local page settings could override OS setting.
- Behavior
- Phones in low power mode may automatically pause auto-playing videos to conserve battery life.
- Testing
- While the
disableremoteplaybackattribute may not be recognized as valid HTML, the error can be safely disregarded.
- Behavior
- When cookie is set to true (paused) and reduce animation is disabled, cookie is not set to false (not paused). Page needs to be refreshed. This may not be an issue, but to work, we may need another condition to hook into here.
- Behavior
- Revisit
data-callbackand see if it makes sense to apply callback function to all animations ifdata-callbackpresent on page at all. Right now, there is an animation disconnect between those that have the attribute and those that do not. Currently, we manually add the callback to all other page animations. - Behavior
- Include a reload button to enable playback of videos that loop only once, allowing users to replay them if desired.
- Behavior
- If
data-pause-removeis used, override it temporarily to include play button, so that video does not obscure content. Especially true when transparent video is used. - Behavior
- When phone is in low power mode the video is paused, but the toggle button does not reflect a paused state. See if video is playing or paused and alter state of toggle button.
- Logic
- Add a condition to check if the video is paused when using the
data-posterattribute. This attribute does not work in low power mode, which can be problematic if the first frame has no content or images. While a quick fix is to use aposterelement, we would prefer to avoid this solution. - Behavior
- Add a possible replay button for videos that only play once.
- Feature
- Add a universal animation toggle button that can live anywhere on the page and be custom styled.
Report Issues
Find a bug? Want a new feature? Report it on JIRA. You're so awesome!