Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions assets/scss/_image-modal_project.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,19 @@
cursor: pointer;
}

/* Native button used as the keyboard-accessible image trigger */
.md__image-trigger {
display: block;
width: 100%;
margin: 0;
padding: 0;
border: 0;
background: transparent;
color: inherit;
font: inherit;
text-align: inherit;
cursor: pointer;
}
/* The Modal (background) */
.modal {
display: none;
Expand Down
56 changes: 46 additions & 10 deletions layouts/_default/_markup/render-image.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,51 @@

<div class="md__image">
{{- with $img }}
{{- $webp := .Process "webp q85" }}
<picture>
<source srcset="{{ $webp.RelPermalink }}" type="image/webp" width="{{ .Width }}" height="{{ .Height }}">
<img src="{{ $dest | safeURL }}" onclick="openModal(this)" alt="{{ $alt }}"
width="{{ .Width }}" height="{{ .Height }}"
class="{{ $class }}" />
</picture>
{{- if reflect.IsImageResourceProcessable . }}
{{- $webp := .Process "webp q85" }}
<button
type="button"
class="md__image-trigger"
aria-label="Expand image{{ with $alt }}: {{ . }}{{ end }}"
>
<picture>
<source
srcset="{{ $webp.RelPermalink }}"
type="image/webp"
>
<img
src="{{ $dest | safeURL }}"
alt="{{ $alt }}"
width="{{ .Width }}"
height="{{ .Height }}"
class="{{ $class }}"
/>
</picture>
</button>
{{- else }}
<button
type="button"
class="md__image-trigger"
aria-label="Expand image{{ with $alt }}: {{ . }}{{ end }}"
>
<img
src="{{ $dest | safeURL }}"
alt="{{ $alt }}"
class="{{ $class }}"
/>
</button>
{{- end }}
{{- else }}
<img src="{{ $dest | safeURL }}" onclick="openModal(this)" alt="{{ $alt }}"
class="{{ $class }}" />
<button
type="button"
class="md__image-trigger"
aria-label="Expand image{{ with $alt }}: {{ . }}{{ end }}"
>
<img
src="{{ $dest | safeURL }}"
alt="{{ $alt }}"
class="{{ $class }}"
/>
</button>
{{- end }}
</div>
</div>
166 changes: 132 additions & 34 deletions layouts/partials/image-modal.html
Original file line number Diff line number Diff line change
@@ -1,61 +1,159 @@
<div id="myModal" class="modal">
<button class="modal-close" onclick="closeModal()">close</button>
<div
id="myModal"
class="modal"
role="dialog"
aria-modal="true"
aria-label="Image preview"
>
<button
type="button"
class="modal-close"
onclick="closeModal()"
aria-label="Close image"
>
close
</button>

<div class="modal-cont">
<img class="modal-pic" id="modalPic" onclick="closeModal()" style="max-width: 100%; max-height: 80vh; margin: auto;" alt="Modal-pic">
<img
class="modal-pic"
id="modalPic"
onclick="closeModal()"
style="max-width: 100%; max-height: 80vh; margin: auto;"
alt="Image preview"
>
</div>
</div>

<script>
// Open the Modal
function openModal(imageIdOrElement) {
var src;
var modalTrigger = null;
var modalFallbackFocus = null;

if (typeof imageIdOrElement === 'string') {
// If it's a string (image ID), get the source using the ID
src = document.getElementById(imageIdOrElement).src;
} else if (imageIdOrElement instanceof HTMLImageElement) {
// If <img>, get the source directly. currentSrc is what the browser actually
// chose, so inside a <picture> the modal reuses the already-fetched WebP
// rather than downloading the larger original.
src = imageIdOrElement.currentSrc || imageIdOrElement.src;
function openModal(image) {
if (!(image instanceof HTMLImageElement)) {
return;
}

var src = image.currentSrc || image.src;

if (src && src.includes("#")) {
src = src.substring(0, src.indexOf("#"));
}

document.getElementById("modalPic").src = src;
document.getElementById("myModal").style.display = "block";
var modal = document.getElementById("myModal");
var modalPic = document.getElementById("modalPic");
var closeButton = modal.querySelector(".modal-close");

modalTrigger = image.closest(".md__image-trigger");
modalFallbackFocus = document.activeElement;

modalPic.src = src;
modalPic.alt = image.alt || "Image preview";
modal.style.display = "block";

if (closeButton) {
closeButton.focus();
}
}

function closeModal() {
var modal = document.getElementById("myModal");
var modalPic = document.getElementById("modalPic");
var trigger = modalTrigger;
var fallbackFocus = modalFallbackFocus;

modalPic.src = "";
modal.style.display = "none";
modalTrigger = null;
modalFallbackFocus = null;

if (trigger && document.contains(trigger)) {
trigger.focus();
} else if (fallbackFocus && document.contains(fallbackFocus)) {
fallbackFocus.focus();
}
}

function isModalOpen() {
return document.getElementById("myModal").style.display === "block";
}

// Close the Modal
function closeModal() {
document.getElementById("modalPic").src = "";
document.getElementById("myModal").style.display = "none";
function trapModalFocus(event) {
if (event.key !== "Tab" || !isModalOpen()) {
return;
}

var modal = document.getElementById("myModal");

var focusableElements = modal.querySelectorAll(
'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'
);

if (!focusableElements.length) {
event.preventDefault();
return;
}

var firstElement = focusableElements[0];
var lastElement = focusableElements[focusableElements.length - 1];

if (event.shiftKey && document.activeElement === firstElement) {
event.preventDefault();
lastElement.focus();
} else if (!event.shiftKey && document.activeElement === lastElement) {
event.preventDefault();
firstElement.focus();
}
}

// Listen for the Escape key to close the modal
document.addEventListener("keydown", function (event) {
if (event.key === "Escape") {
if (event.key === "Escape" && isModalOpen()) {
event.preventDefault();
closeModal();
return;
}

trapModalFocus(event);
});

// Attach onclick attribute to all <img> after DOM is loaded
document.addEventListener("DOMContentLoaded", function () {
var imgTags = document.querySelectorAll("img");
imgTags.forEach(function (img) {
img.onclick = function () {
if (img.dataset.modal !== "false") {
openModal(img);
var modal = document.getElementById("myModal");

var imageTriggers = document.querySelectorAll(".md__image-trigger");

imageTriggers.forEach(function (trigger) {
var link = trigger.closest("a");
var image = trigger.querySelector("img");

if (link && image) {
image.dataset.modal = "false";
trigger.replaceWith(image);
return;
}

trigger.addEventListener("click", function () {
if (image) {
openModal(image);
}
};
});
});

// Close modal when clicking anywhere inside #myModal
var modal = document.getElementById("myModal");
modal.addEventListener("click", function () {
closeModal();
document.querySelectorAll("img:not(.md__image-trigger img)").forEach(function (img) {
if (img.dataset.modal === "false" || img.closest("#myModal")) {
return;
}

img.addEventListener("click", function () {
openModal(img);
});
});

if (modal) {
modal.addEventListener("click", function (event) {
if (event.target === modal) {
closeModal();
}
});
}
});
</script>
</script>
Loading