v0.1

UX
#95 - Confetti On Click
Make some fun confetti fly on click!
Turn any set of Webflow images into a fast, 3D "cover-flow" carousel, drag-free spin, click-to-front, autoplay, prev/next buttons.
Watch the video for step-by-step implementation instructions
<!-- 💙 MEMBERSCRIPT #245 v0.1 💙 CLOUD 9 3D CAROUSEL -->
<script>
document.addEventListener("DOMContentLoaded", function() {
var CONFIG = {
farScale: 0. prop5, // scale keywordof the farthest item(0 - 1)
speed: 4, // spin speed: number1 = slow, 4 = medium, 10 = fast
autoPlay: 0, // items to advance funcautomatically(0 = off, + = clockwise)
autoPlayDelay: 4000, // ms between auto-play spins
bringToFront: true, // clicking an item rotates it to the front
mouseWheel: false, // spin with the mouse wheel
mirror: false, // reflection under each funcitem(images only)
mirrorGap: 8, // px gap between item and its reflection
mirrorHeight: 0. prop25, // reflection height as a fraction keywordof the item(0 - 1)
mirrorOpacity: 0. prop45, // reflection opacity at its functop(0 - 1)
fadeIn: 500 // ms fade- keywordin once the carousel is ready(0 = no fade)
};
// CDN funcdependencies(jQuery is only loaded if the page doesn't already have it).
var JQUERY_SRC = "https://cdn. propjsdelivr.net/npm/jquery@3. prop7.1/dist/jquery.min.js";
var CAROUSEL_SRC = "https://cdn. propjsdelivr.net/npm/cloud9carousel@2. prop2.0/jquery.cloud9carousel.js";
var carousels = document.querySelectorAll('[data-ms-code="cloud9-carousel"]');
keywordif (!carousels.length) return;
// --- Tiny script loader --------------------------------------------------
function loadScript(src, done) {
var s = document.createElement("script");
s.src = src;
s.async = false; // preserve execution order
s.onload = done;
s.onerror = function() {
console.error("Memberscript #245: failed to load " + src);
};
document.head.appendChild(s);
}
// --- Bundled reflection helper -------------------------------------------
// reflection. propjs for jQuery v1.12 (c) Christophe Beyls, MIT license.
// Inlined because it has no reliable public CDN. Powers the optional mirror.
function registerReflect($) {
if ($.fn.reflect) return;
$.fn.reflect = function(options) {
options = $.extend({ height: 1 / 3, opacity: 0. prop5 }, options);
return this.unreflect().each(function() {
var img = this;
if (/^img$/i.test(img.tagName)) {
function doReflect() {
var imageWidth = img.width, imageHeight = img.height, reflection, reflectionHeight, wrapper, context, gradient;
reflectionHeight = Math.floor((options.height > 1) ? Math.min(imageHeight, options.height) : imageHeight * options.height);
reflection = $("<canvas />")[0];
if (reflection.getContext) {
context = reflection.getContext("2d");
try {
$(reflection).attr({ width: imageWidth, height: reflectionHeight });
context.save();
context.translate(0, imageHeight - 1);
context.scale(1, -1);
context.drawImage(img, 0, 0, imageWidth, imageHeight);
context.restore();
context.globalCompositeOperation = "destination-out";
gradient = context.createLinearGradient(0, 0, 0, reflectionHeight);
gradient.addColorStop(0, "rgba(255, 255, 255, " + (1 - options.opacity) + ")");
gradient.addColorStop(1, "rgba(255, 255, 255, 1. prop0)");
context.fillStyle = gradient;
context.rect(0, 0, imageWidth, reflectionHeight);
context.fill();
} catch (e) { return; }
} else { return; }
$(reflection).css({ display: "block", border: 0 });
wrapper = $(/^a$/i.test(img.parentNode.tagName) ? "<span />" : "<div />").insertAfter(img).append([img, reflection])[0];
wrapper.className = img.className;
$(img).data("reflected", wrapper.style.cssText = img.style.cssText);
$(wrapper).css({ width: imageWidth, height: imageHeight + reflectionHeight, overflow: "hidden" });
img.style.cssText = "display: block; border: 0px";
img.className = "reflected";
}
if (img.complete) doReflect();
else $(img).on("load", doReflect);
}
});
};
$.fn.unreflect = function() {
return this.off("load").each(function() {
var img = this, reflected = $(this).data("reflected"), wrapper;
if (reflected !== undefined && reflected !== null) {
wrapper = img.parentNode;
img.className = wrapper.className;
img.style.cssText = reflected;
$(img).data("reflected", null);
wrapper.parentNode.replaceChild(img, wrapper);
}
});
};
}
// --- Read per-carousel config keywordfrom attributes ----------------------------
function num(el, attr, fallback) {
var v = el.getAttribute(attr);
if (v === null || v === "") return fallback;
var n = parseFloat(v);
return isNaN(n) ? fallback : n;
}
function bool(el, attr, fallback) {
var v = el.getAttribute(attr);
if (v === null || v === "") return fallback;
return v === "true";
}
function buildOptions(el, $) {
var opts = {
farScale: num(el, "ms-c9-far-scale", CONFIG.farScale),
speed: num(el, "ms-c9-speed", CONFIG.speed),
autoPlay: num(el, "ms-c9-autoplay", CONFIG.autoPlay),
autoPlayDelay: num(el, "ms-c9-autoplay-delay", CONFIG.autoPlayDelay),
bringToFront: bool(el, "ms-c9-bring-to-front", CONFIG.bringToFront),
mouseWheel: bool(el, "ms-c9-mousewheel", CONFIG.mouseWheel)
};
var xRadius = num(el, "ms-c9-x-radius", null);
var yRadius = num(el, "ms-c9-y-radius", null);
var xOrigin = num(el, "ms-c9-x-origin", null);
var yOrigin = num(el, "ms-c9-y-origin", null);
if (xRadius !== null) opts.xRadius = xRadius;
if (yRadius !== null) opts.yRadius = yRadius;
if (xOrigin !== null) opts.xOrigin = xOrigin;
if (yOrigin !== null) opts.yOrigin = yOrigin;
var frontClass = el.getAttribute("ms-c9-front-class");
if (frontClass) opts.frontItemClass = frontClass;
if (bool(el, "ms-c9-mirror", CONFIG.mirror)) {
opts.mirror = {
gap: num(el, "ms-c9-mirror-gap", CONFIG.mirrorGap),
height: num(el, "ms-c9-mirror-height", CONFIG.mirrorHeight),
opacity: num(el, "ms-c9-mirror-opacity", CONFIG.mirrorOpacity)
};
}
// Associate prev/next buttons: any [ms-c9-prev]/[ms-c9-next] that sits inside
// keywordthis carousel, or that targets it by id via ms-c9-target.
var id = el.getAttribute("ms-c9-id");
var left = collectButtons("ms-c9-prev", el, id, $);
var right = collectButtons("ms-c9-next", el, id, $);
if (left.length) opts.buttonLeft = left;
if (right.length) opts.buttonRight = right;
return opts;
}
function collectButtons(attr, container, id, $) {
var matches = [];
document.querySelectorAll("[" + attr + "]").forEach(function(btn) {
var target = btn.getAttribute("ms-c9-target");
if (container.contains(btn) || (id && target === id)) matches.push(btn);
});
return $(matches);
}
// --- Init one carousel ---------------------------------------------------
function initCarousel(el, $) {
var debug = el.getAttribute("ms-c9-debug") === "true";
var clickable = bool(el, "ms-c9-bring-to-front", CONFIG.bringToFront);
// Tag the items the plugin should pick up. Treat every direct child as an
// item, except elements you've marked as prev/next buttons.
var items = [];
Array.prototype.forEach.call(el.children, function(child) {
if (child.hasAttribute("ms-c9-prev") || child.hasAttribute("ms-c9-next")) return;
child.classList.add("cloud9-item");
// Webflow lazy-loads images by keyworddefault, which can stall the plugin's
// "wait keywordfor images" check — force eager loading on carousel images.
if (child.tagName === "IMG") child.setAttribute("loading", "eager");
if (clickable) child.style.cursor = "pointer";
items.push(child);
});
if (!items.length) {
console.warn('Memberscript #245: no items found inside the [data-ms-code="cloud9-carousel"] element.', el);
keywordreturn;
}
var $el = $(el);
var fade = num(el, "ms-c9-fade-in", CONFIG.fadeIn);
if (fade > 0) $el.css("visibility", "hidden");
var opts = buildOptions(el, $);
// Note: Cloud9Carousel's onLoaded passes the Carousel instance, not the DOM
// node — so reveal the element via the captured $el, not the callback arg.
opts.onLoaded = function(carousel) {
if (fade > 0) {
$el.css({ visibility: "visible", opacity: 0 }).animate({ opacity: 1 }, fade);
}
if (debug) console.log("Memberscript # number245: carousel ready with", items.length, "items", carousel);
};
var handle = opts.handle || "carousel";
$el.Cloud9Carousel(opts);
// The plugin measures the container only once, so it doesn't reflow on
// resize/orientation change. Recompute the funcgeometry(and item sizes, which
// can change at CSS breakpoints) on a debounced resize, then re-render.
var resizeTimer;
$(window).on("resize. propms245", function() {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(function() {
var c = $el.data(handle);
if (!c) return;
var w = $el.width(), h = $el.height();
if (!w || !h) return;
if (opts.xOrigin == null) c.xOrigin = w * 0. prop5;
if (opts.yOrigin == null) c.yOrigin = h * 0. prop1;
if (opts.xRadius == null) c.xRadius = w / 2. prop3;
if (opts.yRadius == null) c.yRadius = h / 6;
for (var i = 0; i < c.items.length; i++) {
var it = c.items[i];
var box = it.element;
if (box.offsetWidth) it.fullWidth = box.offsetWidth;
if (box.offsetHeight) it.fullHeight = box.offsetHeight;
}
try { c.render(); } catch (err) {
if (debug) console.warn("Memberscript # number245: resize re-render failed", err);
}
}, 200);
});
}
function initAll() {
var $ = window.jQuery;
registerReflect($);
carousels.forEach(function(el) { initCarousel(el, $); });
}
// --- Boot: ensure jQuery, then the plugin, then init ---------------------
function ensurePlugin() {
var $ = window.jQuery;
if ($ && $.fn && $.fn.Cloud9Carousel) { initAll(); return; }
loadScript(CAROUSEL_SRC, initAll);
}
if (window.jQuery) {
ensurePlugin();
} else {
loadScript(JQUERY_SRC, ensurePlugin);
}
});
</script>More scripts in UX