<div id="gallery-74529-56938" class="gallery" role="figure" aria-labelledby="gallery-74529-56938-headline">
<div class="gallery__headline" id="gallery-74529-56938-headline">
<h1 class="headline" data-headline-size="6"><span class="headline__text">Gallery (4 images)</span></h1>
</div>
<div class="gallery__preview">
<figure id="media-31108-14052" class="media" role="figure" aria-labelledby="media-31108-14052-caption">
<div class="media__figure">
<div class="media__image">
<picture class="image" style="background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAIAAAACDbGyAAAABGdBTUEAALGPC/xhBQAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAABaADAAQAAAABAAAABQAAAABcYj7LAAAAFUlEQVQIHWP8V8PJgASYkNggJql8AHi/AY1z9PnwAAAAAElFTkSuQmCC)">
<source srcset="https://bildermangel.de/1380x776/fe7c09/130f26.webp?text=+++M:+690x388@2x+++ 2x, https://bildermangel.de/690x388/fe7c09/130f26.webp?text=+++M:+690x388@1x+++ 1x" media="(min-width: 512px) and (max-width: 767px)">
<source srcset="https://bildermangel.de/1760x990/fe7c09/130f26.webp?text=+++L:+880x495@2x+++ 2x, https://bildermangel.de/880x495/fe7c09/130f26.webp?text=+++L:+880x495@1x+++ 1x" media="(min-width: 768px) and (max-width: 1023px)">
<source srcset="https://bildermangel.de/2048x1152/fe7c09/130f26.webp?text=+++XL:+1024x576@2x+++ 2x, https://bildermangel.de/1024x576/fe7c09/130f26.webp?text=+++XL:+1024x576@1x+++ 1x" media="(min-width: 1024px)">
<img class="image__img" src="https://bildermangel.de/470x264/fe7c09/130f26.webp?text=+++S:+470x264@1x+++" srcset="https://bildermangel.de/940x528/fe7c09/130f26.webp?text=+++S:+470x264@2x+++ 2x" width="470" height="264" alt="Das ist ein Platzhalter-Bild" loading="lazy" />
</picture>
</div>
<div class="media__button">
<button class="button u-overlay-link" type="button" data-gallery="gallery-74529-56938-gallery-template">
<span class="button__icon">
<svg class="icon icon--panorama" viewBox="0 0 200 200" aria-hidden="true">
<use xlink:href="/assets/icons/icons.dcaca1c147.svg#panorama"></use>
</svg> </span>
<span class="button__text u-underline">View gallery</span>
</button>
</div>
</div>
<figcaption class="media__caption" id="media-31108-14052-caption">
<div class="media__copyright">
<div class="copyright">
<button class="copyright__button u-overlay-link" type="button" title="Image attribution" aria-pressed="false">
<svg class="icon icon--copyright" viewBox="0 0 200 200" aria-hidden="true">
<use xlink:href="/assets/icons/icons.dcaca1c147.svg#copyright"></use>
</svg> </button>
<div class="copyright__floating" tabindex="0" role="floating" hidden data-floating-placements="bottom,top,left,right">Source: example.com</div>
</div>
</div>
</figcaption>
</figure>
</div>
<template id="gallery-74529-56938-gallery-template">
<div role="dialog" class="gallery__overlay" id="gallery-74529-56938-gallery" aria-label="Gallery">
<div class="gallery__overlay-container" role="document">
<div class="gallery__images">
<div class="gallery__image" tabindex="0" data-floating-root>
<picture class="image">
<img class="image__img" src="https://bildermangel.de/470x264/fe7c09/130f26" width="470" height="264" alt="Das ist ein Platzhalter-Bild" />
</picture>
<div class="gallery__copyright">
<div class="copyright">
<button class="copyright__button" type="button" title="Image attribution" aria-pressed="false">
<svg class="icon icon--copyright" viewBox="0 0 200 200" aria-hidden="true">
<use xlink:href="/assets/icons/icons.dcaca1c147.svg#copyright"></use>
</svg> </button>
<div class="copyright__floating" tabindex="0" role="floating" hidden data-floating-placements="bottom,top,left,right">Quelle: example.com</div>
</div>
</div>
<div class="gallery__caption">Das ist die Bildbeschreibung</div>
</div>
<div class="gallery__image" tabindex="0" data-floating-root hidden>
<picture class="image">
<img class="image__img" src="https://bildermangel.de/264x470/fe7c09/130f26" width="264" height="470" alt="Das ist ein Platzhalter-Bild" />
</picture>
<div class="gallery__copyright">
<div class="copyright">
<button class="copyright__button" type="button" title="Image attribution" aria-pressed="false">
<svg class="icon icon--copyright" viewBox="0 0 200 200" aria-hidden="true">
<use xlink:href="/assets/icons/icons.dcaca1c147.svg#copyright"></use>
</svg> </button>
<div class="copyright__floating" tabindex="0" role="floating" hidden data-floating-placements="bottom,top,left,right">Quelle: example.com</div>
</div>
</div>
<div class="gallery__caption">Das ist die Bildbeschreibung mit <a class="link" data-link-type="external" href="https://example.com" rel="noopener noreferrer" target="_blank"><span class="link__icon"><svg class="icon icon--arrow-up-right-from-square" viewBox="0 0 200 200" aria-hidden="true">
<use xlink:href="/assets/icons/icons.dcaca1c147.svg#arrow-up-right-from-square"></use>
</svg></span><span class="link__text u-underline">einem Link</span></a></div>
</div>
<div class="gallery__image" tabindex="0" data-floating-root hidden>
<picture class="image">
<img class="image__img" src="https://bildermangel.de/470x264/fe7c09/130f26" width="470" height="264" alt="Das ist ein Platzhalter-Bild" />
</picture>
<div class="gallery__copyright">
<div class="copyright">
<button class="copyright__button" type="button" title="Image attribution" aria-pressed="false">
<svg class="icon icon--copyright" viewBox="0 0 200 200" aria-hidden="true">
<use xlink:href="/assets/icons/icons.dcaca1c147.svg#copyright"></use>
</svg> </button>
<div class="copyright__floating" tabindex="0" role="floating" hidden data-floating-placements="bottom,top,left,right">Quelle: example.com</div>
</div>
</div>
<div class="gallery__caption">Das ist die Bildbeschreibung mit <a class="link" data-link-type="external" href="https://example.com" rel="noopener noreferrer" target="_blank"><span class="link__icon"><svg class="icon icon--arrow-up-right-from-square" viewBox="0 0 200 200" aria-hidden="true">
<use xlink:href="/assets/icons/icons.dcaca1c147.svg#arrow-up-right-from-square"></use>
</svg></span><span class="link__text u-underline">einem Link</span></a></div>
</div>
<div class="gallery__image" tabindex="0" data-floating-root hidden>
<picture class="image">
<img class="image__img" src="https://bildermangel.de/264x470/fe7c09/130f26" width="264" height="470" alt="Das ist ein Platzhalter-Bild" />
</picture>
<div class="gallery__copyright">
<div class="copyright">
<button class="copyright__button" type="button" title="Image attribution" aria-pressed="false">
<svg class="icon icon--copyright" viewBox="0 0 200 200" aria-hidden="true">
<use xlink:href="/assets/icons/icons.dcaca1c147.svg#copyright"></use>
</svg> </button>
<div class="copyright__floating" tabindex="0" role="floating" hidden data-floating-placements="bottom,top,left,right">Quelle: example.com</div>
</div>
</div>
<div class="gallery__caption">Das ist die Bildbeschreibung</div>
</div>
</div>
<div class="gallery__controls">
<div class="gallery__close">
<button class="button u-overlay-link" type="button" title="Close gallery" data-gallery-action="close">
<span class="button__icon">
<svg class="icon icon--xmark" viewBox="0 0 200 200" aria-hidden="true">
<use xlink:href="/assets/icons/icons.dcaca1c147.svg#xmark"></use>
</svg> </span>
</button>
</div>
<div class="gallery__position">1/4</div>
<div class="gallery__navigation">
<button class="button" type="button" title="Previous image" data-gallery-action="prev">
<span class="button__icon">
<svg class="icon icon--arrow-left" viewBox="0 0 200 200" aria-hidden="true">
<use xlink:href="/assets/icons/icons.dcaca1c147.svg#arrow-left"></use>
</svg> </span>
</button>
<button class="button" type="button" title="Next image" data-gallery-action="next">
<span class="button__icon">
<svg class="icon icon--arrow-right" viewBox="0 0 200 200" aria-hidden="true">
<use xlink:href="/assets/icons/icons.dcaca1c147.svg#arrow-right"></use>
</svg> </span>
</button>
</div>
</div>
</div>
</div>
</template>
</div>
{% set id = id ?? html_id('gallery') -%}
<div {{ html_attributes({
id: id,
class: 'gallery',
role: 'figure',
'aria-labelledby': 'headline' | namespaceInputId(id),
}, attrs ?? {}) }}>
<div class="gallery__headline" id="{{ 'headline' | namespaceInputId(id) }}">
{% include '@headline' with {
text: '{text} ({count, plural, one {One image} other {# images}})' | t('site', {
text: headline,
count: images | length,
}),
size: 7,
level: level ?? 1,
} only %}
</div>
<div class="gallery__preview">
{% include '@media' with preview | merge({
caption: false,
link: {
text: 'View gallery' | t('site'),
icon: 'panorama',
attrs: {
'data-gallery': 'gallery-template' | namespaceInputId(id),
},
},
}) only %}
</div>
<template id="{{ 'gallery-template' | namespaceInputId(id) }}">
<div role="dialog" class="gallery__overlay" id="{{ 'gallery' | namespaceInputId(id) }}" aria-label="{{ 'Gallery' | t('site') }}">
<div class="gallery__overlay-container" role="document">
<div class="gallery__images">
{% for image in images %}
<div class="gallery__image" tabindex="0" data-floating-root{{ not loop.first ? ' hidden' }}>
{% include '@image' with image | merge({
lazy: false,
placeholder: false,
}) only %}
{% if image.copyright|default %}
<div class="gallery__copyright">
{% include '@copyright' with {
text: image.copyright,
} only %}
</div>
{% endif %}
{% if image.caption|default %}
<div class="gallery__caption">
{{- image.caption | componentize(inline_only=true) -}}
</div>
{% endif %}
</div>
{% endfor %}
</div>
<div class="gallery__controls">
<div class="gallery__close">
{% include '@button' with {
icon: 'xmark',
title: 'Close gallery' | t('site'),
overlay: true,
attrs: {
'data-gallery-action': 'close',
},
} only %}
</div>
<div class="gallery__position">
{{- "1/#{images|length}" -}}
</div>
<div class="gallery__navigation">
{% include '@button' with {
title: 'Previous image' | t('site'),
icon: 'arrow-left',
attrs: {
'data-gallery-action': 'prev',
},
} only %}
{% include '@button' with {
title: 'Next image' | t('site'),
icon: 'arrow-right',
attrs: {
'data-gallery-action': 'next',
},
} only %}
</div>
</div>
</div>
</div>
</template>
</div>
{
"preview": {
"caption": "This is the caption <a href=\"https://example.com\">with a link</a>",
"copyright": "Source: example.com",
"image": {
"src": "https://bildermangel.de/470x264/fe7c09/130f26.webp?text=+++S:+470x264@1x+++",
"width": 470,
"height": 264,
"alt": "Das ist ein Platzhalter-Bild",
"src2x": "https://bildermangel.de/940x528/fe7c09/130f26.webp?text=+++S:+470x264@2x+++",
"placeholder": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAIAAAACDbGyAAAABGdBTUEAALGPC/xhBQAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAABaADAAQAAAABAAAABQAAAABcYj7LAAAAFUlEQVQIHWP8V8PJgASYkNggJql8AHi/AY1z9PnwAAAAAElFTkSuQmCC",
"sources": [
{
"srcset": "https://bildermangel.de/690x388/fe7c09/130f26.webp?text=+++M:+690x388@1x+++",
"srcset2x": "https://bildermangel.de/1380x776/fe7c09/130f26.webp?text=+++M:+690x388@2x+++",
"media": "(min-width: 512px) and (max-width: 767px)"
},
{
"srcset": "https://bildermangel.de/880x495/fe7c09/130f26.webp?text=+++L:+880x495@1x+++",
"srcset2x": "https://bildermangel.de/1760x990/fe7c09/130f26.webp?text=+++L:+880x495@2x+++",
"media": "(min-width: 768px) and (max-width: 1023px)"
},
{
"srcset": "https://bildermangel.de/1024x576/fe7c09/130f26.webp?text=+++XL:+1024x576@1x+++",
"srcset2x": "https://bildermangel.de/2048x1152/fe7c09/130f26.webp?text=+++XL:+1024x576@2x+++",
"media": "(min-width: 1024px)"
}
]
}
},
"headline": "Gallery",
"images": [
{
"src": "https://bildermangel.de/470x264/fe7c09/130f26",
"width": 470,
"height": 264,
"alt": "Das ist ein Platzhalter-Bild",
"caption": "Das ist die Bildbeschreibung",
"copyright": "Quelle: example.com"
},
{
"src": "https://bildermangel.de/264x470/fe7c09/130f26",
"width": 264,
"height": 470,
"alt": "Das ist ein Platzhalter-Bild",
"caption": "Das ist die Bildbeschreibung mit <a href=\"https://example.com\">einem Link</a>",
"copyright": "Quelle: example.com"
},
{
"src": "https://bildermangel.de/470x264/fe7c09/130f26",
"width": 470,
"height": 264,
"alt": "Das ist ein Platzhalter-Bild",
"caption": "Das ist die Bildbeschreibung mit <a href=\"https://example.com\">einem Link</a>",
"copyright": "Quelle: example.com"
},
{
"src": "https://bildermangel.de/264x470/fe7c09/130f26",
"width": 264,
"height": 470,
"alt": "Das ist ein Platzhalter-Bild",
"caption": "Das ist die Bildbeschreibung",
"copyright": "Quelle: example.com"
}
]
}
@use 'layers';
.gallery__headline {
margin-block-end: 1.6rem;
}
.gallery__overlay {
--_gallery-padding: clamp(2rem, 10vw, 3rem);
--_gallery-image-padding-block: 2rem;
--_gallery-close-button-size: 4rem;
--_gallery-navigation-button-size: 4.4rem;
--_gallery-copyright-size: 2.4rem;
--icon-button-size: var(--_gallery-navigation-button-size);
animation: opacity var(--duration-fast), background-blur var(--duration-x-long);
backdrop-filter: blur(10px);
background-color: color-mix(in srgb, var(--color-black), transparent 20%);
color: var(--color-white);
display: flex;
flex-direction: column;
inset: 0;
overflow: hidden;
padding-block-end: calc(var(--_gallery-padding) + var(--_gallery-navigation-button-size) + var(--_gallery-image-padding-block));
padding-block-start: calc(var(--_gallery-padding) + var(--_gallery-close-button-size) + var(--_gallery-image-padding-block));
padding-inline: var(--_gallery-padding);
position: fixed;
z-index: #{layers.position('overlay')};
}
.gallery__overlay-container {
block-size: 100%;
display: flex;
flex-direction: column;
flex-grow: 1;
inline-size: 100%;
}
.gallery__images {
display: flex;
flex-grow: 1;
inline-size: 100%;
justify-content: center;
position: relative;
z-index: 1;
}
.gallery__image {
animation-duration: var(--duration-x-long);
animation-name: opacity;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
inline-size: 100%;
margin-block: auto;
position: relative;
&:is([data-move-direction='left']) {
animation-name: opacity, slide-from-left;
}
&:is([data-move-direction='right']) {
animation-name: opacity, slide-from-right;
}
}
.gallery__copyright {
--copyright-size: var(--_gallery-copyright-size);
inset-block-end: 1rem;
inset-inline-end: 1rem;
line-height: 0;
position: absolute;
z-index: 2;
}
.gallery__caption {
--_gallery-caption-padding-inline: var(--gallery-caption-padding-inline, 1rem);
--selection-foreground-color: var(--color-black);
--selection-background-color: var(--color-white);
--link-color: currentColor;
--link-underline-color: currentColor;
--link-color--enter: currentColor;
--link-underline-color--enter: transparent;
backdrop-filter: blur(10px);
background-color: color-mix(in srgb, var(--color-black), transparent 40%);
border-radius: var(--border-radius-x-small);
font-size: var(--font-size-caption);
inset-block-end: 1rem;
inset-inline-start: 1rem;
line-height: var(--line-height-narrow);
max-inline-size: calc(100% - var(--_gallery-caption-padding-inline) * 2 - var(--_gallery-copyright-size) - 1rem);
padding-block: var(--gallery-caption-padding-block, 0.5rem);
padding-inline: var(--_gallery-caption-padding-inline);
position: absolute;
z-index: 2;
}
.gallery__controls {
inset: 0;
pointer-events: none;
position: absolute;
user-select: none;
z-index: 2;
}
.gallery__close {
inset-block-start: var(--_gallery-padding);
inset-inline-end: var(--_gallery-padding);
line-height: 0;
pointer-events: all;
position: absolute;
}
.gallery__position {
font-size: var(--font-size-default);
font-weight: var(--font-weight-bold);
inset-block-start: var(--_gallery-padding);
inset-inline-start: var(--_gallery-padding);
line-height: var(--_gallery-close-button-size);
position: absolute;
}
.gallery__navigation {
display: flex;
gap: 1rem;
inset-block-end: var(--_gallery-padding);
inset-inline-start: 50%;
pointer-events: all;
position: absolute;
transform: translateX(-50%);
}
import { on } from 'delegated-events';
import { createFocusTrap } from 'focus-trap';
import elementIndex from '../../../javascripts/utils/elementIndex';
import abort from '../../../javascripts/utils/abort';
import moveFocus from '../../../javascripts/utils/moveFocus';
const fitImageInsideContainer = ($image: HTMLElement, $container: HTMLElement) => {
const { width: containerWidth, height: containerHeight } = $container.getBoundingClientRect();
const $img = $image instanceof HTMLImageElement ? $image : $image.querySelector('img');
const { width, height } = $img ?? {};
if (width && height) {
const imageRatio = width / height;
const fittedRatio = containerHeight / (containerWidth / imageRatio);
const maxWidth = Math.min(1, fittedRatio) * 100;
$image.style.maxWidth = `${maxWidth}%`;
}
};
on('click', '[data-gallery]', (triggerEvent) => {
const { currentTarget: $trigger } = triggerEvent;
const { gallery: templateId } = $trigger.dataset;
if (!templateId) {
return;
}
const $gallery = $trigger.closest<HTMLElement>('.gallery') ?? abort();
const $template = document.querySelector<HTMLTemplateElement>(`#${templateId}`) ?? abort();
const $fragment = $template.content.cloneNode(true) as DocumentFragment;
const $overlay = $fragment.querySelector<HTMLElement>('.gallery__overlay') ?? abort();
const $container = $overlay.querySelector<HTMLElement>('.gallery__overlay-container') ?? abort();
const $position = $overlay.querySelector('.gallery__position') ?? abort();
const currentImage = () => $overlay.querySelector<HTMLElement>('.gallery__image:not([hidden])') ?? abort();
const moveImage = (by: number) => {
const $$image = $overlay.querySelectorAll<HTMLElement>('.gallery__image');
const $currentImage = currentImage();
const currentPosition = elementIndex($currentImage) + 1;
let newPosition = currentPosition + by;
if (newPosition > $$image.length) {
newPosition %= $$image.length;
}
if (newPosition <= 0) {
newPosition = $$image.length + newPosition;
}
const $nextImage = $$image[newPosition - 1] ?? abort();
$nextImage.dataset.moveDirection = by > 0 ? 'left' : 'right';
$currentImage.toggleAttribute('hidden', true);
$nextImage.toggleAttribute('hidden', false);
$position.textContent = `${newPosition}/${$$image.length}`;
fitImageInsideContainer($nextImage, $container);
moveFocus($nextImage);
};
const resizeOberserver = new ResizeObserver(() => {
fitImageInsideContainer(currentImage(), $container);
});
const focusTrap = createFocusTrap($overlay, {
clickOutsideDeactivates: false,
escapeDeactivates: true,
onActivate() {
fitImageInsideContainer(currentImage(), $container);
resizeOberserver.observe($overlay);
},
onDeactivate() {
resizeOberserver.disconnect();
},
onPostDeactivate() {
$overlay.remove();
},
});
$overlay.addEventListener('keydown', (event) => {
if (event.code === 'ArrowRight') {
event.preventDefault();
moveImage(1);
} else if (event.code === 'ArrowLeft') {
event.preventDefault();
moveImage(-1);
}
});
$overlay.addEventListener('click', (event) => {
const { target: $target } = event;
if ($target instanceof Element) {
const { galleryAction: action } = $target?.closest<HTMLElement>('[data-gallery-action]')?.dataset ?? {};
if (action === 'prev' || action === 'next') {
event.preventDefault();
moveImage(action === 'prev' ? -1 : 1);
}
if (action === 'close') {
event.preventDefault();
focusTrap.deactivate();
}
}
});
$gallery.appendChild($overlay);
focusTrap.activate();
});
No notes defined.