Skip to content

Commit bc0ccdc

Browse files
authored
Feature/img modal (#283)
* inital working modal * full screen image modal, images with expand icon * add img processing test * modify server svg loading, move client svg to import * move imgModal adding to server * retain list formatting * add tests for image modal processing * remove semicolon * readable includes syntax * entirely client side image modal impl * remove unnecessary comment * refactor imgModal code into a single partial
1 parent b6357d0 commit bc0ccdc

File tree

5 files changed

+163
-1
lines changed

5 files changed

+163
-1
lines changed

custom/README.md

+4
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,10 @@ The site name, logo, and most of the text on the website can be modified from th
8989
placing a value for the same key in `custom/strings.yaml`, with a custom string,
9090
Javascript function, or image path.
9191

92+
## Image Modal
93+
Images can opened/expanded in a modal/dialog. On hover of an image, the cursor will turn into the pointer style, and an expand button will show. Once clicked, the image will center in the page, and a minimize icon will show. Styles for the modal can be found in
94+
`/styles/partials/core/_furniture.scss` as well as one line in `/styles/partials/core/_base.scss` that adds a pointer cursor on hover of images. All HTML/JS for the Image Modal is included in the [`/layouts/partials/imgModal.ejs`](../layouts/partials/imgModal.ejs) file. The Image Modal is then imported in the default layout for content, code for which can be found [here](../layouts/categories/default.ejs#L29).
95+
9296
## Middleware
9397
Middleware can be added to the beginning or end of the request cycle by placing
9498
files into `custom/middleware`. These files can export `preload` and `postload`

layouts/categories/default.ejs

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
<%- include('partials/childrenList', {children, kicker: template('folder.childrenList.kicker', title)}) %>
2727
<% } %>
2828
</div>
29-
29+
<%- include('partials/imgModal') %>
3030
<%- include('partials/footer', { pageType: 'document', topLevelFolder: url.split('/')[1] }) %>
3131
</div>
3232
</body>

layouts/partials/imgModal.ejs

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<div id="image-modal" class="image-modal">
2+
<button type="button" class="close" title="Minimize this image" aria-label="Minimize this image">
3+
<svg width="60" height="60" fill="none"><path fill-rule="evenodd" clip-rule="evenodd" d="M35.97 25.37L38.6 28H32v-6.6l2.63 2.63L38.67 20 40 21.33l-4.03 4.04zM24.03 34.63L21.4 32H28v6.6l-2.63-2.63L21.33 40 20 38.67l4.03-4.04z" fill="#000"/></svg>
4+
</button>
5+
<div class="img-wrapper">
6+
<img class="modal-image" id="img1" />
7+
</div>
8+
<!-- expandIconSvg svg inlined, will be used in image overlay and removed from here on document load. JS in footer.ejs does this-->
9+
<svg id="expandIconSvg" width="60" height="60" fill="#fff"><circle cx="30" cy="30" r="30" fill="#000" fill-opacity=".5"/><path fill-rule="evenodd" clip-rule="evenodd" d="M23.97 37.37L26.6 40H20v-6.6l2.63 2.63L26.67 32 28 33.33l-4.03 4.04zM36.03 22.63L33.4 20H40v6.6l-2.63-2.63L33.33 28 32 26.67l4.03-4.04z"/></svg>
10+
</div>
11+
12+
<script>
13+
var imgWrapper
14+
function createImageOverlayWrapper() {
15+
if (imgWrapper) { // cache hit
16+
return imgWrapper
17+
}
18+
19+
// read expandIcon from image-modal where its inlined so that we
20+
// can read it here. then remove it and strip the id since it'll be used multiple times
21+
const expandIconSvg = document.getElementById('expandIconSvg')
22+
expandIconSvg.remove()
23+
expandIconSvg.id = ""
24+
25+
26+
let wrapperDiv = document.createElement('div')
27+
wrapperDiv.className = 'image-wrapper'
28+
29+
let expandBtn = document.createElement('button')
30+
expandBtn.setAttribute('aria-label', 'expand this image')
31+
expandBtn.title = 'expand this image'
32+
expandBtn.className='expand-image-btn'
33+
expandBtn.appendChild(expandIconSvg)
34+
35+
wrapperDiv.appendChild(expandBtn)
36+
37+
imgWrapper = wrapperDiv // add to cache
38+
39+
return wrapperDiv
40+
}
41+
42+
function wrapImageInOverlay(imgElement) {
43+
// clone the wrapper so the original isn't mutated and we can reuse it
44+
const overlayWrapper = createImageOverlayWrapper().cloneNode(true)
45+
46+
imgElement.parentNode.insertBefore(overlayWrapper, imgElement)
47+
overlayWrapper.appendChild(imgElement)
48+
}
49+
50+
window.onload = () => {
51+
const imgs = document.querySelectorAll('.g-main-content img'); // get all images
52+
53+
// for each image - add overlay allowing for expand btn and click handler for expansion
54+
imgs.forEach((img, idx) => {
55+
wrapImageInOverlay(img);
56+
img.addEventListener('click', () => expandImage(img.src));
57+
});
58+
}
59+
60+
var imgModal = document.querySelector('.image-modal')
61+
var imgModalImg = document.querySelector('.image-modal .modal-image');
62+
63+
function expandImage(imgSrc) {
64+
imgModal.style.display = 'block';
65+
imgModalImg.src = imgSrc;
66+
67+
window.addEventListener('scroll', function onScroll() {
68+
window.removeEventListener('scroll', onScroll);
69+
imgModal.style.display='none';
70+
});
71+
};
72+
73+
var modalCloseBtn = document.querySelector('.image-modal .close');
74+
modalCloseBtn.addEventListener('click', () => {
75+
imgModal.style.display = 'none';
76+
});
77+
78+
/* keydown listener so img modal can be closed with Esc key */
79+
document.addEventListener('keydown', (event) => {
80+
if (event.key === 'Escape' && imgModal.style.display === 'block') {
81+
imgModal.style.display = 'none';
82+
}
83+
});
84+
</script>

styles/partials/core/_categories.scss

+1
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@
271271
padding: 10px;
272272
display: block;
273273
margin: 20px auto;
274+
cursor: pointer; // images can be opened in a modal
274275

275276
@include tablet{
276277
max-width: 580px;

styles/partials/core/_furniture.scss

+73
Original file line numberDiff line numberDiff line change
@@ -451,3 +451,76 @@
451451
text-align: center;
452452
}
453453
}
454+
455+
// the image-wrapper class is added for the benefit of the
456+
// Image Modal, so co-locating it here
457+
.image-wrapper {
458+
position: relative;
459+
460+
.expand-image-btn {
461+
height: 60px;
462+
width: 60px;
463+
position: absolute;
464+
bottom: 25px;
465+
right: 25px;
466+
opacity: 0;
467+
transition: opacity 0.3s ease 0s;
468+
background-color: transparent;
469+
pointer-events: none;
470+
border: none;
471+
}
472+
473+
&:hover {
474+
.expand-image-btn {
475+
opacity: 1;
476+
}
477+
}
478+
}
479+
480+
.image-modal {
481+
display: none;
482+
position: fixed;
483+
z-index: 1000000090; /* search bar icon has z-index of 1*10 */
484+
inset: 0px; /* shorthand for top,right,bottom,left at the same time */
485+
overflow: hidden;
486+
background-color: rgb(255, 255, 255); /* full white allows images to pop */
487+
transition: display 0.2s ease 0s;
488+
}
489+
490+
.image-modal .img-wrapper {
491+
display: flex;
492+
align-items: center;
493+
align-content: center;
494+
height: 100%;
495+
padding: 30px;
496+
497+
.modal-image {
498+
margin: auto;
499+
display: block;
500+
max-width: 100%;
501+
cursor: default;
502+
}
503+
}
504+
505+
.image-modal .close {
506+
display: flex;
507+
align-items: center;
508+
position: absolute;
509+
top: 10px;
510+
right: 10px;
511+
background-color: transparent;
512+
cursor: pointer;
513+
border: 0.5px solid white;
514+
border-radius: 50%;
515+
width: 60px;
516+
height: 60px;
517+
transition: all 0.1s ease-in;
518+
padding: 0px;
519+
520+
&:hover, &:focus {
521+
background-color: #f9f9f9;
522+
border-color: lightgray;
523+
}
524+
}
525+
526+

0 commit comments

Comments
 (0)