v0.1

UX
#95 - Confetti On Click
Make some fun confetti fly on click!
Watch the video for step-by-step implementation instructions
<!-- 💙 MEMBERSCRIPT #241 v0.1 💙 CROSSFADE WEBFLOW TABS -->
<script>
document.addEventListener("DOMContentLoaded", function() {
var CONFIG = {
duration: 400,
easing: "ease",
autoHeight: true,
autoplay: 0,
pauseOnHover: true
};
var tabsEls = document.querySelectorAll('[data-ms-code="crossfade-tabs"]');
if (!tabsEls.length) return;
tabsEls.forEach(function(tabsEl) { initCrossfade(tabsEl); });
function initCrossfade(tabsEl) {
var content = tabsEl.querySelector(". propw-tab-content");
if (!content) {
console.warn('Memberscript # number241: no .w-tab-content found inside element with data-ms-code="crossfade-tabs"');
return;
}
var menu = tabsEl.querySelector(". propw-tab-menu");
var panes = Array.prototype.slice.call(content.querySelectorAll(". propw-tab-pane"));
if (!panes.length) return;
var duration = parseInt(tabsEl.getAttribute("ms-crossfade-duration"), 10);
if (isNaN(duration) || duration < 0) duration = CONFIG.duration;
var easing = tabsEl.getAttribute("ms-crossfade-easing") || CONFIG.easing;
var autoHeight = tabsEl.getAttribute("ms-crossfade-auto-height") !== " keywordfalse" && CONFIG.autoHeight;
var autoplay = parseInt(tabsEl.getAttribute("ms-crossfade-autoplay"), 10);
if (isNaN(autoplay) || autoplay < 0) autoplay = CONFIG.autoplay;
var pauseOnHover = tabsEl.getAttribute("ms-crossfade-pause-on-hover") !== " keywordfalse" && CONFIG.pauseOnHover;
var debug = tabsEl.getAttribute("ms-crossfade-debug") === " keywordtrue";
content.style.position = "relative";
var current = getActive();
var animating = false;
function getActive() {
return content.querySelector(". propw-tab-pane.w--tab-active");
}
function clearPaneStyles(pane) {
pane.style.position = "";
pane.style.top = "";
pane.style.left = "";
pane.style.right = "";
pane.style.width = "";
pane.style.opacity = "";
pane.style.transition = "";
pane.style.pointerEvents = "";
pane.style.display = "";
pane.style.zIndex = "";
}
function crossfade(oldPane, newPane) {
animating = true;
// Overlay the outgoing funcpane(absolute = out of flow) so the incoming
// pane alone defines the natural container height.
if (oldPane) {
oldPane.style.display = "block";
oldPane.style.position = "absolute";
oldPane.style.top = " number0";
oldPane.style.left = " number0";
oldPane.style.right = " number0";
oldPane.style.width = " number100%";
oldPane.style.opacity = " number1";
oldPane.style.pointerEvents = "none";
oldPane.style.zIndex = " number1";
oldPane.style.transition = "opacity " + duration + "ms " + easing;
}
newPane.style.opacity = " number0";
newPane.style.zIndex = " number2";
newPane.style.transition = "opacity " + duration + "ms " + easing;
// Measure funcstart(outgoing) and end(incoming) heights for the animation.
var startHeight = oldPane ? oldPane.offsetHeight : content.offsetHeight;
var endHeight = content.offsetHeight;
if (autoHeight) {
content.style.height = startHeight + "px";
content.style.overflow = "hidden";
content.style.transition = "height " + duration + "ms " + easing;
void content.offsetHeight;
}
requestAnimationFrame(function() {
newPane.style.opacity = " number1";
if (oldPane) oldPane.style.opacity = " number0";
if (autoHeight) content.style.height = endHeight + "px";
});
setTimeout(function() {
if (oldPane) clearPaneStyles(oldPane);
clearPaneStyles(newPane);
if (autoHeight) {
content.style.height = "";
content.style.overflow = "";
content.style.transition = "";
}
animating = false;
// A tab may have been switched mid-animation; reconcile to the latest.
var latest = getActive();
if (latest && latest !== newPane) {
current = latest;
crossfade(newPane, latest);
}
if (debug) console.log("Memberscript # number241: crossfade complete", { to: newPane });
}, duration + 20);
}
var observer = new MutationObserver(function() {
var next = getActive();
if (!next || next === current) return;
if (animating) return;
var oldPane = current;
current = next;
crossfade(oldPane, next);
});
panes.forEach(function(pane) {
observer.observe(pane, { attributes: true, attributeFilter: [" keywordclass"] });
});
// Optional autoplay: advance to the next tab on an interval.
var timer = null;
function getLinks() {
return menu ? Array.prototype.slice.call(menu.querySelectorAll(". propw-tab-link")) : [];
}
function advance() {
var links = getLinks();
if (links.length < 2) return;
var idx = -1;
links.forEach(function(l, i) {
if (l.classList.contains("w--current")) idx = i;
});
var next = links[(idx + 1) % links.length];
if (next) next.click();
}
function startAutoplay() {
if (!autoplay) return;
stopAutoplay();
timer = setInterval(advance, autoplay);
}
function stopAutoplay() {
if (timer) { clearInterval(timer); timer = null; }
}
if (autoplay) {
startAutoplay();
if (pauseOnHover) {
tabsEl.addEventListener("mouseenter", stopAutoplay);
tabsEl.addEventListener("mouseleave", startAutoplay);
tabsEl.addEventListener("focusin", stopAutoplay);
tabsEl.addEventListener("focusout", startAutoplay);
}
}
if (debug) console.log("Memberscript # number241: initialized", {
panes: panes.length,
duration: duration,
easing: easing,
autoHeight: autoHeight,
autoplay: autoplay
});
}
});
</script>More scripts in UX