April 16, 2021

Build A Modal From Scratch

Hola. 👋🏾

While redoing an earlier version of my website, again 😅, I decided it would be only HTML, CSS and JavaScript. Below is an example of how I made a modal, using just those 3 technologies. (Not including media queries for responsiveness)

You can also view the repo with a gif of the code in action, here.

index.html

1<!DOCTYPE html>
2<html lang='en'>
3
4<head>
5 <meta charset='UTF-8'>
6 <meta name='viewport' content='width=device-width, initial-scale=1'>
7 <title>Build A Modal From Scratch</title>
8 <link href='index.css' rel='stylesheet' type='text/css'>
9</head>
10
11<body>
12 <main class='container'>
13 <h1>Click the image below</h1>
14 <section id='projects'>
15 <div class='blackout'></div>
16 <img class='project-button' src='./assets/safer.png' alt='safeR web app' data-project-button='safer'>
17 <aside class='popup-modal safer' data-project-modal='safer'>
18 <div class='popup-modal__close'>
19 Close
20 </div>
21 <div class='modal-mock-up safer'></div>
22 <div class='app-details'>
23 <h2 class='app-name safer'>safeR</h2>
24 </div>
25 <p class='app-blurb'>
26 safeR is a React app with a React Native
27 companion, complete with geolocation, that
28 allows users to anonymously report local
29 incidents.
30 </p>
31 </aside>
32 </section>
33 </main>
34</body>
35<script src='index.js'></script>
36
37</html>

index.css

1/********
2DEFAULT MOBILE STYLING
3********/
4* {
5 margin: 0;
6 padding: 0;
7}
8
9*,
10*:before,
11*:after {
12 box-sizing: inherit;
13}
14
15a {
16 text-decoration: none;
17 color: inherit;
18}
19
20.app-blurb {
21 font-size: 14px;
22 text-align: center;
23 margin: 20px auto;
24 width: 300px;
25}
26
27.app-details {
28 display: flex;
29 margin: 15px auto;
30 align-items: center;
31 justify-content: center;
32}
33
34.app-name {
35 font-size: 30px;
36 font-weight: 100;
37 letter-spacing: 1px;
38 margin-left: 10px;
39}
40
41.blackout {
42 position: absolute;
43 z-index: 1010;
44 left: 0px;
45 width: 100%;
46 height: 100%;
47 background-color: rgba(0, 0, 0, 0.65);
48 display: none;
49}
50
51body.hidden {
52 overflow: hidden;
53}
54
55.container {
56 padding-top: 35vh;
57 display: flex;
58 flex-direction: column;
59 align-items: center;
60}
61
62html {
63 box-sizing: border-box;
64}
65
66.modal-mock-up {
67 background-size: cover;
68 background-repeat: no-repeat;
69 width: 330px;
70 height: 300px;
71 margin: 0 auto;
72}
73
74.popup-modal {
75 height: 100%;
76 width: 100%;
77 position: absolute;
78 left: 0px;
79 padding: 20px;
80 display: none;
81 -webkit-transition: all 300ms ease-in-out;
82 transition: all 300ms ease-in-out;
83 z-index: 1011;
84}
85
86.popup-modal.is--visible {
87 pointer-events: auto;
88 display: initial;
89}
90
91.popup-modal__close {
92 font-size: 20px;
93 text-align: center;
94 margin-left: 350px;
95 cursor: pointer;
96}
97
98.project-button {
99 display: inline-block;
100 width: 300px;
101 margin: 20px auto;
102}
103
104.proof {
105 height: 20px;
106 width: 20px;
107 margin-right: 10px;
108}
109
110.proof-wrapper {
111 justify-content: center;
112 display: flex;
113 bottom: 40px;
114 position: absolute;
115 margin-left: auto;
116 margin-right: auto;
117 left: 0;
118 right: 0;
119}

index.js

1'use strict'
2
3const height = window.innerHeight
4const width = window.innerWidth
5
6const dividers = document.querySelectorAll('.divider')
7const sections = document.querySelectorAll('section')
8
9dividers.forEach(divider => divider.style.setProperty('--divider-width', width))
10sections.forEach(section => {
11 section.style.setProperty('--section-height', height)
12 section.style.setProperty('--section-width', width)
13})
14
15
16// projects
17const blackout = document.querySelector('.blackout')
18const body = document.querySelector('body')
19const modalOpenTriggers = document.querySelectorAll('.project-button')
20
21modalOpenTriggers.forEach(trigger => {
22 trigger.addEventListener('click', () => {
23 const { projectButton } = trigger.dataset
24 const popupModal = document.querySelector(`[data-project-modal='${projectButton}']`)
25
26 setModalStyle(projectButton)
27 addClasses(popupModal)
28
29 popupModal.scrollIntoView()
30
31 popupModal.querySelector('.popup-modal__close').addEventListener('click', () => removeClasses(popupModal))
32 blackout.addEventListener('click', () => removeClasses(popupModal))
33 })
34})
35
36const setModalStyle = project => {
37 const modalBgImages = document.getElementsByClassName('modal-mock-up')
38 const modalBgImage = [...modalBgImages].find(image => image.classList.contains(`${project}`))
39 const appNames = document.getElementsByClassName('app-name')
40 const appName = [...appNames].find(name => name.classList.contains(`${project}`))
41 const modalBgColors = document.getElementsByClassName('popup-modal')
42 const modalBgColor = [...modalBgColors].find(color => color.classList.contains(`${project}`))
43
44 let image
45 let name
46 let color
47
48 switch(project) {
49 case 'safer':
50 image = 'url(assets/safer-modal.png)'
51 color = '#7BC087'
52 }
53
54 modalBgImage.style['background-image'] = image
55 appName.style.color = name
56 modalBgColor.style['background-color'] = color
57}
58
59
60const addClasses = modal => {
61 modal.classList.add('is--visible')
62 blackout.classList.add('is-blacked-out')
63 body.classList.add('hidden')
64}
65
66const removeClasses = modal => {
67 modal.classList.remove('is--visible')
68 blackout.classList.remove('is-blacked-out')
69 body.classList.remove('hidden')
70}

See you next time. 🙃