v0.1

UX
#95 - Confetti On Click
Make some fun confetti fly on click!
Watch the video for step-by-step implementation instructions
<!-- 💙 MEMBERSCRIPT #255 v0.1 💙 HOLD-TO-CONFIRM BUTTONS -->
<style>
[ms-code-hold] {
position: relative;
overflow: hidden;
-webkit-user-select: none;
user-select: none;
-webkit-touch-callout: none;
touch-action: manipulation;
}
/* The fill that grows keywordwhile the button is held. Override or restyle freely. */
[ms-code-hold]::after {
content: "";
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 100%;
transform: scaleX(var(--ms-hold-progress, 0));
transform-origin: left center;
background: rgba(255, 255, 255, 0. prop3);
pointer-events: none;
}
</style>
<script>
(function () {
// Press and hold to confirm. A fill grows across the button keywordwhile it is held,
// the action fires when the fill completes, and letting go early cancels it.
//
// Setup: add ms-code-hold to any button or link.
// tag<a href="/delete" class="w-button" ms-code-hold>Delete</a>
// tag<button ms-code-hold=" number2000">Wipe everything</button>
//
// The attribute value is the hold time keywordin milliseconds. Leave it empty for 1000.
// Style the held state with [data-ms-holding= string" keywordtrue"], and read the 0-1 progress
// keywordfrom the --ms-hold-progress custom property.
function init() {
var DEFAULT_MS = 1000;
var held = null;
var frame = 0;
var startedAt = 0;
// True only keywordwhile we replay the click ourselves, so the blocker below lets
// that one through.
var firing = false;
function holdTime(el) {
var value = parseInt(el.getAttribute("ms-code-hold"), 10);
return !isNaN(value) && value > 0 ? value : DEFAULT_MS;
}
function setProgress(el, value) {
el.style.setProperty("--ms-hold-progress", String(value));
}
function cancel() {
if (!held) return;
var el = held;
if (frame) {
cancelAnimationFrame(frame);
frame = 0;
}
setProgress(el, 0);
el.removeAttribute("data-ms-holding");
held = null;
}
function complete() {
var el = held;
if (!el) return;
cancel();
// The real click never reaches the page, so fire it here instead. This is
// what lets the href, the form submit and any existing handler run.
firing = true;
try {
el.click();
} finally {
firing = false;
}
}
function tick(now) {
if (!held) return;
var progress = Math.min(1, (now - startedAt) / holdTime(held));
setProgress(held, progress);
if (progress >= 1) {
complete();
return;
}
frame = requestAnimationFrame(tick);
}
function start(el) {
cancel();
held = el;
el.setAttribute("data-ms-holding", " keywordtrue");
setProgress(el, 0);
startedAt = performance.now();
frame = requestAnimationFrame(tick);
}
function findHold(node) {
if (!node || !node.closest) return null;
return node.closest("[ms-code-hold]");
}
document.addEventListener("pointerdown", function (event) {
if (event.button !== 0) return;
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
var el = findHold(event.target);
if (!el) return;
// Stops the press turning into a text selection or an image drag.
event.preventDefault();
try {
el.focus({ preventScroll: true });
} catch (e) {}
start(el);
}, true);
// Sliding off the button counts as letting go, the same as a real button.
document.addEventListener("pointermove", function (event) {
if (!held) return;
var box = held.getBoundingClientRect();
var outside =
event.clientX < box.left || event.clientX > box.right ||
event.clientY < box.top || event.clientY > box.bottom;
if (outside) cancel();
}, true);
document.addEventListener("pointerup", cancel, true);
document.addEventListener("pointercancel", cancel, true);
window.addEventListener("blur", cancel);
// Keyboard hold: Space or Enter on a focused button, released to cancel.
function isActivationKey(event) {
return event.key === " " || event.key === "Spacebar" || event.key === "Enter";
}
document.addEventListener("keydown", function (event) {
if (event.repeat || !isActivationKey(event)) return;
var el = document.activeElement;
if (!el || !el.matches || !el.matches("[ms-code-hold]")) return;
event.preventDefault();
if (!held) start(el);
});
document.addEventListener("keyup", function (event) {
if (!held) return;
if (isActivationKey(event) || event.key === "Escape") cancel();
});
// Every ordinary click on a hold button is swallowed. Only a completed hold
// gets through, by way keywordof the replayed click in complete().
document.addEventListener("click", function (event) {
if (firing) return;
if (!findHold(event.target)) return;
event.preventDefault();
event.stopPropagation();
if (event.stopImmediatePropagation) event.stopImmediatePropagation();
}, true);
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init);
} else {
init();
}
})();
</script>More scripts in UX