From: Arnaud Guéras Date: Sun, 24 Nov 2024 11:15:09 +0000 (+0100) Subject: Fix carousel X-Git-Url: http://xenbits.xensource.com/gitweb?a=commitdiff_plain;h=7b1fc4eb740b711099dbe4afb9dfa9e0a4eb7ecb;p=www-xenproject-org.git Fix carousel --- diff --git a/content/test.md b/content/test.md index c57a2b0..b4ff9d7 100644 --- a/content/test.md +++ b/content/test.md @@ -3,75 +3,8 @@ title: "test" draft: true hidden: true --- - -{{
}} - {{}} - The Xen community is a dynamic and collaborative ecosystem comprised of developers, researchers, and enthusiasts dedicated to advancing open-source virtualization technology. -With diverse backgrounds and expertise, members actively contribute to Xen's evolution, fostering innovation, sharing knowledge, and supporting one another through continuous development efforts. - -

- Get started -

- {{
}} -{{
}} - -{{
}} - - {{}} - The Xen community is a dynamic and collaborative ecosystem comprised of developers, researchers, and enthusiasts dedicated to advancing open-source virtualization technology. -With diverse backgrounds and expertise, members actively contribute to Xen's evolution, fostering innovation, sharing knowledge, and supporting one another through continuous development efforts. - -

- Get started -

- {{
}} -{{
}} - - -{{
}} - {{}}` - animate="true" - - >}} - -The Xen Project Hypervisor is uniquely placed to support a new range of use cases, building on top of 14 years of usage within the data center. In particular, its isolation and security features, flexible virtualization mode and architecture, driver disaggregation, and ARM support (only 47K lines of code) make it a perfect fit for embedded applications. - -{{}} -{{
}} - -{{
}} - {{}} -The high-level goal of Unikraft is to be able to build unikernels targeted at specific applications without requiring the time-consuming, expert work that building such a unikernel requires today. An additional goal (or hope) of Unikraft is that all developers interested in unikernel development would contribute by supplying libraries rather than working on independent projects with different code bases as it is done now. -{{}} -{{
}} - -{{
}} - {{}} -HVMI stands for Hypervisor-based Memory Introspection. The technology leverages Virtual Machine Introspection (VMI) APIs in the Xen and KVM hypervisors. By gaining introspection of the raw memory of running guest virtual machines, HVMI can apply security logic to detect and prevent the use of common attack techniques, such as buffer overflows, heap spray, code injection, and so-on. -{{}} -{{
}} +{{
}} +{{}} +{{}} +{{}} +{{
}} \ No newline at end of file diff --git a/hugo_stats.json b/hugo_stats.json index cebe371..9452c7f 100644 --- a/hugo_stats.json +++ b/hugo_stats.json @@ -78,6 +78,7 @@ "carousel-container", "carousel-container-width", "carousel-content", + "carousel-content-inner", "carousel-item", "col", "color-txt-base", @@ -87,7 +88,6 @@ "container", "container-full", "content-markdown", - "date", "description", "download-search", "fa", diff --git a/themes/xen-project/assets/css/components/carousel.scss b/themes/xen-project/assets/css/components/carousel.scss index 37de028..fbe61e4 100644 --- a/themes/xen-project/assets/css/components/carousel.scss +++ b/themes/xen-project/assets/css/components/carousel.scss @@ -1,33 +1,37 @@ @use "sass:math"; .carousel-container { - --min-height: 100px; --content-padding: 12px; - --item-width: auto; - --item-position: 0; --max-width: none; - --number-of-items: 1; - --space-between-items: 40px; - --height: auto; - padding-bottom: 40px; + --gap: 40px; + --cols: 1; + --items-before: 1; + --items-after: 1; + --total-cols: calc(var(--cols) + var(--items-before) + var(--items-after)); + --item-width: calc((100% - (var(--total-cols) - 1) * var(--gap)) / var(--total-cols)); + --mask-size: var(--item-width); + --negative-margin-multiplier: 3; + --carousel-negative-margin: calc((var(--item-width) + var(--gap)) * -1 * var(--negative-margin-multiplier)); + --color-background: var(--color-surface-secondary); overflow: hidden; @include phone { - --number-of-items: 2; + //--cols: 2; } @include tablet { - --number-of-items: 2; + --cols: 2; --max-width: calc(var(--container-width) + var(--content-padding) * 2); --content-padding: 80px; + --negative-margin-multiplier: 1.66; } @include tablet-up { - --number-of-items: 3; + --cols: 3; } @include desktop { - --number-of-items: 3; + --cols: 3; } .carousel-content { @@ -38,22 +42,63 @@ margin: 0 auto; } + .carousel-item--clone { + visibility: hidden; + pointer-events: none; + } + + .carousel-item { + scroll-snap-align: start; + + &--hidden { + pointer-events: none; + } + } + + .carousel-content { + width: 100%; + position: relative; + } + + .carousel-content-inner { + --opacity: 0.2; + --mask-image: linear-gradient( + to right, + transparent, + rgba(0, 0, 0, var(--opacity)) calc(var(--mask-size) - 5%), + black calc(var(--mask-size) + 5%), + black calc(100% - var(--mask-size) - 5%), + rgba(0, 0, 0, var(--opacity)) calc(100% - var(--mask-size) + 5%), + transparent + ); + mask-image: var(--mask-image); + -webkit-mask-image: var(--mask-image); + margin-left: var(--carousel-negative-margin); + margin-right: var(--carousel-negative-margin); + overflow: hidden; + } + .carousel { display: flex; flex-wrap: wrap; - gap: var(--space-between-items); - align-items: stretch; - position: relative; - height: var(--height); + gap: var(--gap); + overflow-x: scroll; + scroll-snap-type: x mandatory; + scrollbar-width: none; + + &::-webkit-scrollbar { + display: none; + } + + html.has-js & { + flex-wrap: nowrap; + } &-item { - --index: 0; - --transform: calc(var(--item-position) * (var(--index))); - flex: 1 0 calc(100% / var(--number-of-items) - var(--space-between-items)); - top: 0; - min-height: var(--min-height); - left: 0; width: var(--item-width); + min-width: var(--item-width); + max-width: var(--item-width); + flex: 1 1 var(--item-width); } } @@ -92,14 +137,3 @@ font-size: 1.25rem; } } - -.carousel-initialized .carousel-item { - position: absolute; - transform: translateX(var(--transform)); - opacity: 0; - display: none; - transition: - transform 0.5s ease, - opacity 0.8s ease; - height: 100%; -} diff --git a/themes/xen-project/assets/css/molecules/media-block.scss b/themes/xen-project/assets/css/molecules/media-block.scss index 1033b28..6565858 100644 --- a/themes/xen-project/assets/css/molecules/media-block.scss +++ b/themes/xen-project/assets/css/molecules/media-block.scss @@ -34,16 +34,6 @@ &:has(video) { --media-h-padding: 0 !important; } - // &::before { - // content: ""; - // position: absolute; - // top: 0; - // left: 50%; - // width: 1px; - // height: 100%; - // background-color: red; - // transform: translateX(-50%); - // } &.image-small { .media-block__media { diff --git a/themes/xen-project/assets/js/carousel.js b/themes/xen-project/assets/js/carousel.js index d6c59ea..ddef777 100644 --- a/themes/xen-project/assets/js/carousel.js +++ b/themes/xen-project/assets/js/carousel.js @@ -1,227 +1,63 @@ (() => { - const /* The `selector` constant is storing a CSS selector string that is used to select elements in - the DOM. In this code snippet, the `selector` constant is set to `".carousel-container"`, - which means it is targeting elements with the class name "carousel-container". This selector - is then used to find and initialize carousel functionality on those elements in the - document. */ - selector = ".carousel-container"; + document.documentElement.classList.add("has-js"); + const selector = ".carousel-container"; + const itemsContainerSelector = ".carousel"; const itemSelector = ".carousel-item"; - const itemsBefore = 2; - const itemsAfter = 1; const { debounce } = window.XenSiteUtils; const carousel = async (element) => { - const uniqueid = Math.random().toString(36).substring(2, 15); - element.classList.add("carousel-container-" + uniqueid); - const infiniteLoop = true; - const itemsBefore = 2; - const itemsAfter = 2; - - const carouselElement = element.querySelector(".carousel"); - let prev = element.querySelector(".prev"); - let next = element.querySelector(".next"); - let items = await waitForElements(carouselElement, itemSelector); - - // add attributes to first and last items - items[0].dataset.first = true; - items[items.length - 1].dataset.last = true; - - const carouselClone = carouselElement.cloneNode(true); - carouselClone.classList.add("carousel-clone"); - carouselClone.style.setProperty("position", "absolute"); - carouselClone.style.setProperty("left", "0"); - carouselClone.style.setProperty("top", "-100px"); - carouselClone.style.setProperty("width", "100%"); - carouselClone.style.setProperty("overflow", "hidden"); - carouselClone.style.setProperty("flex-wrap", "wrap"); - carouselClone.style.setProperty("pointer-events", "none"); - carouselClone.style.setProperty("visibility", "hidden"); - carouselClone.style.setProperty("height", "100"); - - const getItemInformations = () => { - carouselElement.before(carouselClone); - carouselClone.style.setProperty("width", carouselElement.offsetWidth + "px"); - const items = carouselClone.querySelectorAll(itemSelector); - if (!items.length) return 0; - const item2 = items[1]; - let occupiedSpace = item2.offsetLeft; - - if (occupiedSpace === 0 && item2.offsetTop > 0) occupiedSpace = item2.offsetWidth + 40; - const width = item2.offsetWidth; - const height = [...items].reduce((acc, item) => (acc < item.offsetHeight ? item.offsetHeight : acc), 0); - carouselClone.remove(); - - return { - occupiedSpace, - width, - height, - }; - }; - - // element.classList.add("carousel-start-init"); - - // add the clone the last item to the first place and for the first item to the last place - // generic function to clone N last items to the first place and N first items to the last place - - const cloneItems = (items, clones) => { - for (let i = 0; i <= clones; i++) { - carouselElement.appendChild(items[i].cloneNode(true)); - } - }; - cloneItems(items, itemsBefore); - - const cloneItemsReverse = (items, clones) => { - for (let i = 0; i <= clones; i++) { - carouselElement.prepend(items[items.length - 1 - i].cloneNode(true)); - } - }; - - cloneItemsReverse(items, itemsAfter); - - const moveNext = function () { - if ( - !infiniteLoop && - element.querySelectorAll(itemSelector)[element.querySelectorAll(itemSelector).length - 1].dataset.last - ) - return; - let items = element.querySelectorAll(itemSelector); - carouselElement.appendChild(items[0]); - }; - next.addEventListener("click", moveNext); - - const movePrev = function () { - let items = element.querySelectorAll(itemSelector); - const item = items[itemsBefore]; - if (!infiniteLoop && item.dataset.first) return; - carouselElement.prepend(items[items.length - 1]); - }; - prev.addEventListener("click", movePrev); - - // mobile - let startX, moveX; - - carouselElement.addEventListener("touchstart", (e) => { - startX = e.touches[0].clientX; + const itemsContainer = element.querySelector(itemsContainerSelector); + const items = element.querySelectorAll(itemSelector); + + const firstItem = items[0].cloneNode(true); + firstItem.innerHTML = ""; + firstItem.classList.add("carousel-item--clone"); + itemsContainer.prepend(firstItem); + const lastItem = firstItem.cloneNode(true); + itemsContainer.append(lastItem); + + element.querySelector(".carousel-button.prev").addEventListener("click", () => { + itemsContainer.scrollBy({ + left: -firstItem.clientWidth, + behavior: "smooth", + }); }); - carouselElement.addEventListener("touchend", (e) => { - moveX = e.changedTouches[0].clientX - startX; - if (Math.abs(moveX) > 50) { - if (moveX > 0) { - movePrev(); - } else { - moveNext(); - } - } + element.querySelector(".carousel-button.next").addEventListener("click", () => { + itemsContainer.scrollBy({ + left: firstItem.clientWidth, + behavior: "smooth", + }); }); + }; - /** The rules are generated from the element width and container width - * The rules are : - * - nth-child(1) is opacity:0 and index -2 - * - nth-child(2) is opacity:0 and index -1 - * - nth-child(3) is opacity:1 and index 0 - * - nth-child(4) is opacity:1 and index 1 - * - ... - * - nth-child(maxItems) is opacity:1 and index maxItems - 1 - * - */ - let styleTag; - let lastWindowWidth = -1; - const generateStyles = (element) => { - const windowWidth = window.innerWidth; - if (windowWidth === lastWindowWidth) return; - lastWindowWidth = windowWidth; - const rules = []; - - // add carousel styles generated from the element width - if (!styleTag) { - styleTag = document.createElement("style"); - document.head.appendChild(styleTag); - } - - const { occupiedSpace, width: itemWidth, height } = getItemInformations(); - - if (occupiedSpace < 100) { - console.error("Error in the carousel, no item width detected"); - return; - } + [...document.querySelectorAll(selector)].forEach((elm) => { + carousel(elm); + }); - rules.push(` - .carousel-container-${uniqueid} { - --item-width: ${itemWidth}px; - --item-position: ${occupiedSpace}px; - --height: ${height}px; - } - `); + function updateCarouselTabIndexes() { + const items = document.querySelectorAll(".carousel-item"); - const carouselWidth = carouselElement.offsetWidth; - const maxItems = Math.floor(carouselWidth / occupiedSpace) + 1 + itemsBefore + itemsAfter; + items.forEach((item) => { + const rect = item.getBoundingClientRect(); + const isVisible = rect.left >= 0 && rect.right <= window.innerWidth; - let opacity = 0; - for (let i = 1; i <= maxItems; i++) { - if ((i >= itemsBefore && i < maxItems - itemsAfter) || i - itemsBefore === 0) { - opacity = 1; - } else if (i === maxItems - itemsAfter || i === itemsBefore - 1) { - opacity = 0.2; - } else { - opacity = 0; - } + item.classList.toggle("carousel-item--hidden", !isVisible); + const links = item.querySelectorAll("a"); - const index = i - itemsBefore; - rules.push(` - .carousel-item:nth-child(${i}) { - --index: ${index}; - opacity: ${opacity}; - display: flex; + links.forEach((link) => { + if (link.getAttribute("aria-hidden") !== "true") { + if (isVisible) { + link.removeAttribute("tabindex"); + } else { + link.setAttribute("tabindex", "-1"); } - `); - } - - styleTag.innerHTML = rules.join("\n"); - }; - - window.addEventListener( - "resize", - debounce(() => { - generateStyles(element); - }, 50), - ); - generateStyles(element); - - carouselElement.classList.add("carousel-initialized"); - }; - - /** - * Waits for elements matching the selector to be present in the DOM within the given element. - * @param {HTMLElement} element - The parent element to observe for the selector. - * @param {string} selector - The CSS selector to match the elements. - * @returns {Promise>} A promise that resolves with the matched elements. - */ - const waitForElements = (element, selector) => { - return new Promise((resolve) => { - const items = element.querySelectorAll(selector); - if (items.length) { - resolve(items); - return; - } - - const observer = new MutationObserver((mutations) => { - const items = element.querySelectorAll(selector); - if (items.length) { - observer.disconnect(); - resolve(items); } }); - - observer.observe(element, { - childList: true, - subtree: true, - }); }); - }; + } - [...document.querySelectorAll(selector)].forEach((elm) => { - carousel(elm); - }); + document.querySelector(".carousel").addEventListener("scroll", updateCarouselTabIndexes); + updateCarouselTabIndexes(); })(); diff --git a/themes/xen-project/assets/js/carousel.js.bak b/themes/xen-project/assets/js/carousel.js.bak new file mode 100644 index 0000000..d6c59ea --- /dev/null +++ b/themes/xen-project/assets/js/carousel.js.bak @@ -0,0 +1,227 @@ +(() => { + const /* The `selector` constant is storing a CSS selector string that is used to select elements in + the DOM. In this code snippet, the `selector` constant is set to `".carousel-container"`, + which means it is targeting elements with the class name "carousel-container". This selector + is then used to find and initialize carousel functionality on those elements in the + document. */ + selector = ".carousel-container"; + const itemSelector = ".carousel-item"; + const itemsBefore = 2; + const itemsAfter = 1; + + const { debounce } = window.XenSiteUtils; + + const carousel = async (element) => { + const uniqueid = Math.random().toString(36).substring(2, 15); + element.classList.add("carousel-container-" + uniqueid); + const infiniteLoop = true; + const itemsBefore = 2; + const itemsAfter = 2; + + const carouselElement = element.querySelector(".carousel"); + let prev = element.querySelector(".prev"); + let next = element.querySelector(".next"); + let items = await waitForElements(carouselElement, itemSelector); + + // add attributes to first and last items + items[0].dataset.first = true; + items[items.length - 1].dataset.last = true; + + const carouselClone = carouselElement.cloneNode(true); + carouselClone.classList.add("carousel-clone"); + carouselClone.style.setProperty("position", "absolute"); + carouselClone.style.setProperty("left", "0"); + carouselClone.style.setProperty("top", "-100px"); + carouselClone.style.setProperty("width", "100%"); + carouselClone.style.setProperty("overflow", "hidden"); + carouselClone.style.setProperty("flex-wrap", "wrap"); + carouselClone.style.setProperty("pointer-events", "none"); + carouselClone.style.setProperty("visibility", "hidden"); + carouselClone.style.setProperty("height", "100"); + + const getItemInformations = () => { + carouselElement.before(carouselClone); + carouselClone.style.setProperty("width", carouselElement.offsetWidth + "px"); + const items = carouselClone.querySelectorAll(itemSelector); + if (!items.length) return 0; + const item2 = items[1]; + let occupiedSpace = item2.offsetLeft; + + if (occupiedSpace === 0 && item2.offsetTop > 0) occupiedSpace = item2.offsetWidth + 40; + const width = item2.offsetWidth; + const height = [...items].reduce((acc, item) => (acc < item.offsetHeight ? item.offsetHeight : acc), 0); + carouselClone.remove(); + + return { + occupiedSpace, + width, + height, + }; + }; + + // element.classList.add("carousel-start-init"); + + // add the clone the last item to the first place and for the first item to the last place + // generic function to clone N last items to the first place and N first items to the last place + + const cloneItems = (items, clones) => { + for (let i = 0; i <= clones; i++) { + carouselElement.appendChild(items[i].cloneNode(true)); + } + }; + cloneItems(items, itemsBefore); + + const cloneItemsReverse = (items, clones) => { + for (let i = 0; i <= clones; i++) { + carouselElement.prepend(items[items.length - 1 - i].cloneNode(true)); + } + }; + + cloneItemsReverse(items, itemsAfter); + + const moveNext = function () { + if ( + !infiniteLoop && + element.querySelectorAll(itemSelector)[element.querySelectorAll(itemSelector).length - 1].dataset.last + ) + return; + let items = element.querySelectorAll(itemSelector); + carouselElement.appendChild(items[0]); + }; + next.addEventListener("click", moveNext); + + const movePrev = function () { + let items = element.querySelectorAll(itemSelector); + const item = items[itemsBefore]; + if (!infiniteLoop && item.dataset.first) return; + carouselElement.prepend(items[items.length - 1]); + }; + prev.addEventListener("click", movePrev); + + // mobile + let startX, moveX; + + carouselElement.addEventListener("touchstart", (e) => { + startX = e.touches[0].clientX; + }); + + carouselElement.addEventListener("touchend", (e) => { + moveX = e.changedTouches[0].clientX - startX; + if (Math.abs(moveX) > 50) { + if (moveX > 0) { + movePrev(); + } else { + moveNext(); + } + } + }); + + /** The rules are generated from the element width and container width + * The rules are : + * - nth-child(1) is opacity:0 and index -2 + * - nth-child(2) is opacity:0 and index -1 + * - nth-child(3) is opacity:1 and index 0 + * - nth-child(4) is opacity:1 and index 1 + * - ... + * - nth-child(maxItems) is opacity:1 and index maxItems - 1 + * + */ + let styleTag; + let lastWindowWidth = -1; + const generateStyles = (element) => { + const windowWidth = window.innerWidth; + if (windowWidth === lastWindowWidth) return; + lastWindowWidth = windowWidth; + const rules = []; + + // add carousel styles generated from the element width + if (!styleTag) { + styleTag = document.createElement("style"); + document.head.appendChild(styleTag); + } + + const { occupiedSpace, width: itemWidth, height } = getItemInformations(); + + if (occupiedSpace < 100) { + console.error("Error in the carousel, no item width detected"); + return; + } + + rules.push(` + .carousel-container-${uniqueid} { + --item-width: ${itemWidth}px; + --item-position: ${occupiedSpace}px; + --height: ${height}px; + } + `); + + const carouselWidth = carouselElement.offsetWidth; + const maxItems = Math.floor(carouselWidth / occupiedSpace) + 1 + itemsBefore + itemsAfter; + + let opacity = 0; + for (let i = 1; i <= maxItems; i++) { + if ((i >= itemsBefore && i < maxItems - itemsAfter) || i - itemsBefore === 0) { + opacity = 1; + } else if (i === maxItems - itemsAfter || i === itemsBefore - 1) { + opacity = 0.2; + } else { + opacity = 0; + } + + const index = i - itemsBefore; + rules.push(` + .carousel-item:nth-child(${i}) { + --index: ${index}; + opacity: ${opacity}; + display: flex; + } + `); + } + + styleTag.innerHTML = rules.join("\n"); + }; + + window.addEventListener( + "resize", + debounce(() => { + generateStyles(element); + }, 50), + ); + generateStyles(element); + + carouselElement.classList.add("carousel-initialized"); + }; + + /** + * Waits for elements matching the selector to be present in the DOM within the given element. + * @param {HTMLElement} element - The parent element to observe for the selector. + * @param {string} selector - The CSS selector to match the elements. + * @returns {Promise>} A promise that resolves with the matched elements. + */ + const waitForElements = (element, selector) => { + return new Promise((resolve) => { + const items = element.querySelectorAll(selector); + if (items.length) { + resolve(items); + return; + } + + const observer = new MutationObserver((mutations) => { + const items = element.querySelectorAll(selector); + if (items.length) { + observer.disconnect(); + resolve(items); + } + }); + + observer.observe(element, { + childList: true, + subtree: true, + }); + }); + }; + + [...document.querySelectorAll(selector)].forEach((elm) => { + carousel(elm); + }); +})(); diff --git a/themes/xen-project/assets/js/utils.js b/themes/xen-project/assets/js/utils.js index 62e59e8..2dd00a8 100644 --- a/themes/xen-project/assets/js/utils.js +++ b/themes/xen-project/assets/js/utils.js @@ -29,8 +29,38 @@ }; }; + /** + * Waits for elements matching the selector to be present in the DOM within the given element. + * @param {HTMLElement} element - The parent element to observe for the selector. + * @param {string} selector - The CSS selector to match the elements. + * @returns {Promise>} A promise that resolves with the matched elements. + */ + const waitForElements = (element, selector) => { + return new Promise((resolve) => { + const items = element.querySelectorAll(selector); + if (items.length) { + resolve(items); + return; + } + + const observer = new MutationObserver((mutations) => { + const items = element.querySelectorAll(selector); + if (items.length) { + observer.disconnect(); + resolve(items); + } + }); + + observer.observe(element, { + childList: true, + subtree: true, + }); + }); + }; + window.XenSiteUtils = { formatDate, debounce, + waitForElements, }; })(); diff --git a/themes/xen-project/layouts/partials/carousel.html b/themes/xen-project/layouts/partials/carousel.html index dd88ce4..6ef7da6 100644 --- a/themes/xen-project/layouts/partials/carousel.html +++ b/themes/xen-project/layouts/partials/carousel.html @@ -1,7 +1,9 @@