WEBLEB
Accueil
Éditeur
Connexion
Pro
Français
English
Français
Español
Português
Deutsch
Italiano
हिंदी
Effets du menu déroulant
1104
Andev.web
Ouvrir dans l'éditeur
Publiez votre code
Recommandé
13 November 2024
menu de navigation
30 November 2024
Site Web de voyage avec bordure-rayon inversé
30 January 2025
Page de destination du chef personnel
HTML
Copy
Andev Web
Windows
Mac
Linux
CSS
Copy
@charset "UTF-8"; * { border: 0; box-sizing: border-box; margin: 0; padding: 0; } :root { --hue: 223; --bg: hsl(0,0%,100%); --fg: hsl(var(--hue),10%,10%); --trans-dur: 0.3s; --trans-ease-in-out: cubic-bezier(0.65,0,0.35,1); --trans-ease-out: cubic-bezier(0.33,1,0.68,1); font-size: calc(20px + (40 - 20) * (100vw - 280px) / (3840 - 280)); } body, button { color: var(--fg); font: 1em/1.5 "DM Sans", sans-serif; transition: background-color var(--trans-dur), color var(--trans-dur); } body { background-color: var(--bg); display: flex; height: 100vh; } main { margin: auto; padding: 0.75em 0; width: min-content; height: 12em; } .drop { --drop-trans-dur: 0.5s; --drop-flare-dist: 0; border-radius: 0.5em; margin: auto; padding: 0.25em 0.25em 0 0.25em; position: relative; min-width: 9em; } .drop, .drop:after { background-color: hsl(var(--hue), 10%, 90%); transition: background-color var(--trans-dur); } .drop:before, .drop:after { content: ""; position: absolute; } .drop:before { background-image: radial-gradient(100% 100% at 100% 50%, hsla(var(--hue), 90%, 50%, 0.5), hsla(var(--hue), 90%, 50%, 0) 50%); display: none; right: 0; bottom: 100%; width: 9em; height: 9em; } .drop:after { border-radius: 0.4375em; display: block; top: 0.125em; left: 0.125em; width: calc(100% - 0.25em); height: calc(100% - 0.25em); } .drop__btn { background-color: white; border-radius: 0.375em; box-shadow: 0 0 0 0.25em hsla(var(--hue), 90%, 50%, 0); cursor: pointer; display: flex; align-items: center; line-height: 1; height: 2.5em; outline: transparent; margin-bottom: 0.25em; padding: 0.75em; position: relative; width: 100%; transition: background-color var(--trans-dur), box-shadow calc(var(--trans-dur) / 2) var(--trans-ease-in-out), color var(--drop-trans-dur); -webkit-appearance: none; appearance: none; -webkit-tap-highlight-color: transparent; z-index: 1; } .drop__btn:hover, .drop__btn:focus-visible, .drop__btn[aria-expanded=true] { background-color: hsl(var(--hue), 10%, 95%); } .drop__btn:focus-visible { box-shadow: 0 0 0 0.25em hsla(var(--hue), 90%, 50%, 1); } .drop__btn:after { border-top: 0.375em solid currentColor; border-right: 0.375em solid transparent; border-left: 0.375em solid transparent; content: ""; display: block; margin-inline-start: auto; width: 0; height: 0; transform-origin: 50% 37.5%; transition: transform var(--drop-trans-dur) var(--trans-ease-out); } .drop__btn[aria-expanded=true]:after { transform: rotate(0.5turn); } .drop--collapsing, .drop--expanding, .drop__items { overflow: hidden; } .drop__items { height: 0; } .drop__items-inner { visibility: hidden; } .drop--collapsing .drop__btn[aria-expanded=true] { background-color: white; } .drop--collapsing .drop__btn[aria-expanded=true]:after { transform: rotate(0); } .drop--expanding:before { display: block; } .drop__items .drop__btn:hover, .drop__items .drop__btn:focus-visible { background-color: hsl(var(--hue), 10%, 97.5%); color: hsla(var(--hue), 90%, 50%); } .drop__items .drop__btn:focus-visible { box-shadow: 0 0 0 0.25em hsla(var(--hue), 90%, 50%, 0); } .drop__items .drop__btn:after { border: 0; content: "✓"; display: none; width: auto; height: auto; } .drop__items .drop__btn--selected:after { display: block; } .drop__btn[aria-expanded=true] ~ .drop__items { height: auto; } .drop__btn[aria-expanded=true] ~ .drop__items .drop__items-inner { visibility: visible; } .drop:has([aria-expanded=true]) { height: auto; } .drop:has([aria-expanded=true]):before { transform: translateY(calc(9em + var(--drop-flare-dist))); transition: transform var(--drop-trans-dur) linear; } /* Dark theme */ @media (prefers-color-scheme: dark) { :root { --bg: hsl(var(--hue),10%,10%); --fg: hsl(var(--hue),10%,90%); } .drop, .drop:after { background-color: black; } .drop__btn { background-color: hsl(var(--hue), 10%, 10%); } .drop__btn:hover, .drop__btn:focus-visible, .drop__btn[aria-expanded=true] { background-color: hsl(var(--hue), 10%, 20%); } .drop__items .drop__btn:hover, .drop__items .drop__btn:focus-visible { background-color: hsl(var(--hue), 10%, 15%); color: hsla(var(--hue), 90%, 70%); } .drop--collapsing .drop__btn[aria-expanded=true] { background-color: hsl(var(--hue), 10%, 10%); } }
JS
Copy
"use strict"; window.addEventListener("DOMContentLoaded", () => { const drop = new DropdownMenu("#dummy"); }); class DropdownMenu { /** * @param el CSS selector of the menu */ constructor(el) { var _a, _b, _c; /** Animation objects */ this.animations = []; /** Options for this menu */ this.options = [ { name: "windows", friendlyName: "Windows" }, { name: "mac", friendlyName: "Mac" }, { name: "linux", friendlyName: "Linux" } ]; /** Selected option by name */ this.selected = "windows"; /** Menu is collapsing */ this.isCollapsing = false; /** Menu is expanding */ this.isExpanding = false; /** Actions to run after collapsing the item. */ this.animActionsCollapse = { onfinish: this.onAnimationFinish.bind(this, false), oncancel: () => { this.isCollapsing = false; } }; /** Actions to run after expanding the item. */ this.animActionsExpand = { onfinish: this.onAnimationFinish.bind(this, true), oncancel: () => { this.isExpanding = false; } }; this.el = document.querySelector(el); this.menuButton = (_a = this.el) === null || _a === void 0 ? void 0 : _a.querySelector("button"); this.itemList = (_b = this.el) === null || _b === void 0 ? void 0 : _b.querySelector("[data-items]"); this.defaultOption(); document.addEventListener("click", this.outsideToClose.bind(this)); window.addEventListener("keydown", this.escToClose.bind(this)); (_c = this.el) === null || _c === void 0 ? void 0 : _c.addEventListener("click", this.toggle.bind(this)); window.addEventListener("keydown", this.kbdAction.bind(this)); } /** Transition duration specific to the menu */ get transDuration() { if (this.el) { const style = getComputedStyle(this.el); const rawDur = style.getPropertyValue("--drop-trans-dur"); let dur = rawDur.substring(0, rawDur.indexOf("s")); const mIndex = dur.indexOf("m"); if (mIndex > -1) { dur = dur.substring(0, mIndex); return +dur; } // seconds to milliseconds return +dur * 1e3; } return 0; } /** Display the default selected option. */ defaultOption() { var _a; const buttonEl = (_a = this.itemList) === null || _a === void 0 ? void 0 : _a.querySelector(`[value="${this.selected}"]`); buttonEl === null || buttonEl === void 0 ? void 0 : buttonEl.classList.add("drop__btn--selected"); if (this.menuButton) { const optionFound = this.options.find(option => option.name === this.selected); this.menuButton.textContent = (optionFound === null || optionFound === void 0 ? void 0 : optionFound.friendlyName) || ""; } } /** * Navigate the menu options with arrow keys. * @param e Keydown event */ kbdAction(e) { var _a; const { key } = e; const tabOrArrow = key === "Tab" || key === "ArrowUp" || key === "ArrowDown"; const notAnimating = !this.isExpanding && !this.isCollapsing; if (notAnimating && ((_a = this.menuButton) === null || _a === void 0 ? void 0 : _a.ariaExpanded) === "true" && tabOrArrow) { this.navigateOption(e); } } /** * Press Esc to close the menu. * @param e Keydown event */ escToClose(e) { var _a; if (e.key === "Escape" && (!this.isCollapsing && ((_a = this.menuButton) === null || _a === void 0 ? void 0 : _a.ariaExpanded) === "true")) { this.toggle(e); } } /** * Click outside the menu to close. * @param e Click event */ outsideToClose(e) { var _a; let target = e.target; let elFound = false; if (!this.isCollapsing && ((_a = this.menuButton) === null || _a === void 0 ? void 0 : _a.ariaExpanded) === "true") { do { target = target.parentElement; if (target === this.el) { elFound = true; } } while (target); if (!elFound) { this.toggle(e); } } } /** * Navigate the menu options with arrow keys. * @param e Keydown event */ navigateOption(e) { var _a; const itemList = (_a = this.el) === null || _a === void 0 ? void 0 : _a.querySelector("[data-items]"); const buttonEls = itemList === null || itemList === void 0 ? void 0 : itemList.querySelectorAll("button"); const buttons = Array.from(buttonEls || []); const buttonsTemp = [...buttons]; // for getting the first and last const first = buttonsTemp.shift(); const last = buttonsTemp.pop(); const currentItem = document.activeElement; const { key, shiftKey } = e; const downKey = key === "ArrowDown"; const upKey = key === "ArrowUp"; const currentIndex = buttons.indexOf(currentItem); if (!buttons.length) { // do nothing for no items e.preventDefault(); } else if (downKey) { // next item, go to the first if on the last e.preventDefault(); const nextIndex = currentIndex + 1; if (nextIndex >= buttons.length) { first === null || first === void 0 ? void 0 : first.focus(); return; } buttons[nextIndex].focus(); } else if (upKey) { // previous item, go to the last if on the first e.preventDefault(); const prevIndex = currentIndex - 1; if (prevIndex < 0) { last === null || last === void 0 ? void 0 : last.focus(); return; } buttons[prevIndex].focus(); } else if (buttons.length === 1 || ((!(itemList === null || itemList === void 0 ? void 0 : itemList.contains(currentItem)) || currentItem === last) && !shiftKey)) { // go to the first item if on the last e.preventDefault(); first === null || first === void 0 ? void 0 : first.focus(); } else if ((!(itemList === null || itemList === void 0 ? void 0 : itemList.contains(currentItem)) || currentItem === first) && shiftKey) { // go to the last item if on the first e.preventDefault(); last === null || last === void 0 ? void 0 : last.focus(); } } /** * Open or close the menu. * @param e Click event */ toggle(e) { var _a, _b; e.preventDefault(); (_a = this.el) === null || _a === void 0 ? void 0 : _a.classList.remove("drop--collapsing", "drop--expanding"); const shouldExpand = ((_b = this.menuButton) === null || _b === void 0 ? void 0 : _b.ariaExpanded) === "true"; if (this.isCollapsing || !shouldExpand) { this.expand(); } else if (this.isExpanding || shouldExpand) { this.collapse(e); } } /** Expand the menu. */ expand() { var _a, _b; if (!this.el || !this.itemList) return; this.itemList.style.height = `${this.itemList.offsetHeight}px`; (_a = this.menuButton) === null || _a === void 0 ? void 0 : _a.setAttribute("aria-expanded", "true"); this.el.classList.add("drop--expanding"); this.isExpanding = true; // reset the animations this.animations.forEach(anim => anim.cancel()); this.animations = []; const buttonEls = this.itemList.querySelectorAll("button"); const buttons = Array.from(buttonEls || []); // animate the menu height const startHeight = this.itemList.offsetHeight || 0; const endHeight = ((_b = this.itemList.firstElementChild) === null || _b === void 0 ? void 0 : _b.offsetHeight) || 0; const itemListAnim = this.itemList.animate({ height: [`${startHeight}px`, `${endHeight}px`] }, { duration: this.transDuration, easing: "cubic-bezier(0.33,1,0.68,1.33)" }); itemListAnim.onfinish = this.animActionsExpand.onfinish; itemListAnim.oncancel = this.animActionsExpand.oncancel; this.animations.push(itemListAnim); // animate the buttons buttons.forEach((button, i) => { // animate the buttons const buttomAnim = button.animate({ transform: ["translateY(100%)", "translateY(0)"] }, { duration: this.transDuration, delay: this.transDuration / 12 * i, easing: "cubic-bezier(0.33,1,0.68,1)" }); this.animations.push(buttomAnim); }); // animate the flare this.el.style.setProperty("--drop-flare-dist", `${endHeight}px`); } /** * Collapse the menu. * @param e Click event */ collapse(e) { var _a, _b, _c; if (!this.el || !this.itemList) return; this.el.classList.add("drop--collapsing"); this.isCollapsing = true; // reset the animations this.animations.forEach(anim => anim.cancel()); this.animations = []; const clickedButton = e.target; const buttonEls = (_a = this.itemList) === null || _a === void 0 ? void 0 : _a.querySelectorAll("button"); const buttons = Array.from(buttonEls || []); // animate the menu height const startHeight = ((_b = this.itemList) === null || _b === void 0 ? void 0 : _b.offsetHeight) || 0; const endHeight = 0; const easing = "cubic-bezier(0.33,1,0.68,1)"; const itemListAnim = this.itemList.animate({ height: [`${startHeight}px`, `${endHeight}px`] }, { duration: this.transDuration, easing }); itemListAnim.onfinish = this.animActionsCollapse.onfinish; itemListAnim.oncancel = this.animActionsCollapse.oncancel; this.animations.push(itemListAnim); // animate the buttons buttons.forEach((button, i) => { if (clickedButton.value) { // remove the previous checkmark after selecting an option button.classList.remove("drop__btn--selected"); } // animate the buttons const delayInc = this.transDuration / 12; const buttomAnim = button.animate({ transform: ["translateY(0)", "translateY(100%)"] }, { duration: this.transDuration, delay: delayInc * (buttons.length - 1) - (delayInc * i), easing }); this.animations.push(buttomAnim); }); if (clickedButton.value) { // set this menu’s option from a `value` clickedButton.classList.add("drop__btn--selected"); if (this.menuButton) { const optionFound = this.options.find(option => option.name === clickedButton.value); this.menuButton.textContent = (optionFound === null || optionFound === void 0 ? void 0 : optionFound.friendlyName) || ""; } } (_c = this.menuButton) === null || _c === void 0 ? void 0 : _c.focus(); // animate the flare this.el.style.setProperty("--drop-flare-dist", `${endHeight}px`); } /** * Actions to run when the animation is finished * @param open Menu should be expanded */ onAnimationFinish(open) { var _a, _b; if (!this.el || !this.itemList) return; (_a = this.menuButton) === null || _a === void 0 ? void 0 : _a.setAttribute("aria-expanded", `${open}`); this.animations = []; this.isCollapsing = false; this.isExpanding = false; this.itemList.style.height = ""; (_b = this.el) === null || _b === void 0 ? void 0 : _b.classList.remove("drop--collapsing", "drop--expanding"); } }