You don't have access to this class

Keep learning! Join and start boosting your career

Aprovecha el precio especial y haz tu profesión a prueba de IA

Antes: $249

Currency
$209
Suscríbete

Termina en:

1 Días
7 Hrs
15 Min
46 Seg

Modal: evento click con JavaScript

10/12
Resources

How are events handled in JavaScript?

Events in JavaScript are essential for interacting with users and responding to their actions. One of the most common events is the 'click' event. This event is essential in any interaction that requires opening popup windows or 'modals'. Here we will see how to bind a click event to a button to manage the opening of a modal.

How to implement a click event?

To implement the click event, you need to associate a function that will be executed when a button is clicked. You do this with the addEventListener method. In the context of our example, we are going to associate the button with a function on click, modifying CSS classes to display the modal.

Button.addEventListener('click', () => { modal.classList.remove('hidden'); modal.classList.add('visible');});

This code snippet removes the 'hidden' class and adds the 'visible' class, thus making the modal display. The 'hidden' and 'visible' classes control the visibility of the modal by managing style properties such as display and opacity.

How to manage the visibility of the modal?

To ensure that the modal has the desired appearance and is positioned correctly on the screen, it is crucial to work with the CSS classes. This is where display: grid comes in handy, as it allows you to center the elements inside the modal.

.modal-container { background: white; width: 50%; height: 50%; display: grid; place-items: center;}

The place-items: center; function centers the content horizontally and vertically, ensuring that your modal is aesthetically pleasing in the interface.

How to close the modal?

Closing the modal is a similar process to the one we use to open it. We can include a closing icon (for example, an "x") and associate it with a click event that hides the modal. This is where using icons8.com comes in to get free icons that can be integrated into your project.

Adding an icon to close the modal

The closing icon is obtained and included in the HTML. Using the following code, we associate a click event that hides the modal by removing the 'visible' class and adding 'hidden'.

closeButton.addEventListener('click', () => { modal.classList.add('hidden'); modal.classList.remove('visible');});

Considerations about specificity and the use of !important

When modifying classes to control visibility, a CSS specificity issue can arise. Sometimes it is necessary to use !important to ensure that the rules are applied independently of the other classes.

.hidden { display: none !important;}.visible { display: grid;}

Although !important is not always good practice, in some cases, such as this one, it is useful to overcome specificity issues.

Challenge: Practicing with other elements

A great way to solidify your understanding is to practice associating click events with other buttons or elements on the same page. Try replicating this modal behavior with different superheroes, creating separate modals for each of them.

Don't forget to explore and experiment with design and functionality variations - keep practicing and improving your JavaScript skills to create dynamic and engaging user interfaces!

Contributions 24

Questions 3

Sort by:

Want to see more contributions, questions and answers from the community?

Para el ejemplo no necesariamente tienen que utilizar el !important , solo pongan que el display sea manejado únicamente por las clases .hidden y .visible

   .hidden {
        display: none;
      }

      .visible {
        display: grid;
      }

      .modal {
        width: 100%;
        height: 100%;
        background: rgba(0, 0, 0, 0.5);
        position: absolute;
        z-index: 4;
        
        place-items: center;
      }

Para no crear un evento para cada super heroe en lugar de id podriamos usar una misma clase para los 3 superheroes, y usar querySelectorAll

Cambie un poco el diseño para no tener exactamente el mismo del curso.



HTML

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="style.css">
    <title>Document</title>
</head>
<body>
    <div class="page">
        <!-- Main Content -->
        <section class="container">
            <nav class="navbar">
                <div class="navbar__content">
                    <img src="./img/logo.svg" alt="logo spiderman">
                    <ul>
                        <li><a href="">Exclusivos</a></li>
                        <li><a href="">Nuevos</a></li>
                        <li><a href="">Gift Cards</a></li>
                        <li><a href="">Busca Una Tienda</a></li>
                    </ul>
                </div>
            </nav>

            <section class="main-content">
                <p>Diseña tu</p>
                <h1>Spiderman!</h1>
                <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Necessitatibus dolores sint nobis numquam, eos accusantium ducimus aut quisquam pariatur, consectetur labore ut hic voluptates asperiores quidem, corrupti error ullam amet.</p>
            </section>

            <section class="side-content">
                <div></div>
                <p>Spiderman</p>
                <img src="./img/spidermanAside.png" alt="">
            </section>

            <section class="footer">
                <div class="openModal">
                    <img src="./img/spidermanFooter1.png" alt="spiderman">
                </div>
                <div class="openModal">
                    <img src="./img/spidermanFooter2.png" alt="spiderman">
                </div>
                <div class="openModal">
                    <img src="./img/spidermanFooter3.png" alt="spiderman">
                </div>
            </section>
        </section>

        <!-- Loader -->
        <section class="loader">
            <div><img src="./img/load.svg" alt="imagen logo spiderman"></div>
            <div><img src="./img/load.svg" alt="imagen logo spiderman"></div>
            <div><img src="./img/load.svg" alt="imagen logo spiderman"></div>
        </section>

        <!-- Modal -->
        <section class="modal hidden">
            <div class="modal__content hidden">
                <img class="modal__content--close" src="https://img.icons8.com/external-flat-icons-inmotus-design/67/000000/external-close-basic-work-elements-flat-icons-inmotus-design.png"/>
            </div>
        </section>

    </div>
</body>
<script src="app.js"></script>
</html>

CSS

/*
    1. Posicionamiento
    2. Modelo de caja
    3. Tipografia
    4. Visuales
    5. Otros
*/

@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@100&display=swap");


:root{
    --primario:#222831;
    --secundario:#393E46;
    --terciario:#00ADB5;
    --acentuar:#EEEEEE;
    --spiderman:#a71814;
}

html{
    font-size: 62.2%;
    font-family: "Roboto", sans-serif;
}
*{
    box-sizing: border-box;
    margin: 0;
    padding: 0;
    text-decoration: none;
    list-style: none;
}
body{
    font-size: 1.5rem;
    height: 100vh;
    overflow: hidden;
    background: var(--primario);
}
body,a{
    color: var(--acentuar);
}
/* LOADER */
.page{
    width: 100%;
    height: 100vh;
    position: relative;
}
.container{
    position: absolute;
    z-index: 1;
}
.loader{
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-direction: row;
    background: var(--primario);
    position: absolute;
    z-index: 3;
}
.loader div{
    width: 8rem;
    height: 8rem;
    /* border-radius: 50%; */
    margin: 1rem;
    background-image: url();
}

/* LOADER - ANIMATION */
/* FONDO */
.loader{
    animation: loader 3s linear forwards;
    opacity: 1;   
    visibility: visible;
}
@keyframes loader{
    0%,95%{
        opacity: 1;   
        visibility: visible;
    }
    100%{
        opacity: 0;
        visibility: hidden;
    }
}
/* PUNTOS DE CARGA */
.loader div{
    animation: scaling 1.5s linear infinite;
    transform: scale(1);
}
.loader div:nth-child(1){
    animation-delay: 0.2s;
}
.loader div:nth-child(2){
    animation-delay: 0.5s;
}
.loader div:nth-child(3){
    animation-delay: 0.8s;
}
@keyframes scaling {
    0%,100%{
        transform: scale(1);
    }
    30%,80%{
        transform: scale(0.5);
    }
}

/* GRID PRINCIPAL */
.container{
    width: 100%;
    height: 100%;
    display: grid;
    grid-template-columns: 55% 45%;
    grid-template-rows: 20% 50% 30%;
    grid-template-areas: 
    "nav        aside"
    "section    aside"
    "footer     aside"
}

/* NAVBAR */
.navbar{
    width: 100%;
    height: 100%;
    display: flex;
    place-content: center;
    grid-area: nav;
    border-bottom: .1rem solid var(--acentuar);
}
/* POSICIONAMIENTO DE NAVBAR */
.navbar__content{
    width: 100%;
    padding: 0 0 0 8rem;
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 2rem;
    font-weight: 300;
}
/* ORGANIZANDO EL LOGO */
.navbar__content img{
    width: 6rem;
    padding: .8rem;
    border: .1rem solid var(--acentuar);
    border-radius: 1.2rem;
    background: var(--spiderman);   
}
/* ORGANIZANDO LAS RUTAS/URL/OPCIONES DE NUESTRO MENU */
.navbar__content ul{
    width: 100%;
    margin-block-start: .8rem;
    display: flex;
    justify-content: space-between;
}
.navbar__content ul li{
    padding: .8rem;
    border-radius: 1rem;
    font-weight: 700;
}
.navbar__content ul li:first-child{
    border: .1rem solid var(--acentuar);
    color: var(--secundario);
    background: var(--terciario);
}
.navbar__content ul li:hover{
    border: .1rem solid var(--acentuar);
    background: var(--terciario);
}
.navbar__content ul li:hover > a {
    color: var(--primario);
}


/* NAVBAR - ANIMATION */
.navbar__content{
    animation: 1.5s navbar 3s ease-in-out forwards;
    transform: translateY(-20rem);
}
@keyframes navbar {
    0%{
        transform: translateY(-20rem);
    }
    100%{
        transform: translateY(0);
    }
    
}

/* MAIN CONTENT */
.main-content{
    grid-area:section ;
    display: flex;
    flex-direction: column;
    justify-content: center;
    padding: 0 0 0 8rem; 
}
.main-content p:first-child{
    margin: 0 0 -1.4rem 2.8rem;
    font-size: 3.6rem;
    font-weight: 300;
}
.main-content h1{
    margin: 0 0 2.8rem 0;
    font-size: 4.8rem;
    font-weight: 700;
}
.main-content p:last-child{
    width: 80%;
    font-weight: 100;
}

/* MAIN CONTENT - ANIMATION */
.main-content{
    animation: 1.5s main-content 3s ease-in-out forwards;
    transform: translateX(-100rem);
}
@keyframes main-content {
    0%{
        transform: translatex(-100rem);
    }
    100%{
        transform: translateY(0);
    }
    
}

/* SIDE CONTENT  */
.side-content{
    grid-area: aside;
    display: grid;
    grid-template-columns: repeat(6,1fr);
    align-items: center;
}
/* SIDE CONTENT TEXT */
.side-content p{
    font-size: 3.6rem;
    grid-row: 1 / 2;
    grid-column: 1 / 2;
    transform: rotate(-90deg);
}

/* MAIN CONTENT TEXT - ANIMATION */
.side-content p{
    animation: 1.5s side-content-text 4s ease-in-out forwards;
    opacity: 0;
}
@keyframes side-content-text {
    0%{
        opacity: 0;
    }
    100%{
        opacity: 1;
    }
    
}

/* SIDE CONTENT IMG */
.side-content img{
    width: 100%;
    grid-row: 1 / 2;
    grid-column: 2 / 6;
}

/* MAIN CONTENT IMG - ANIMATION */
.side-content img{
    animation: 1.5s side-content-img 4s ease-in-out forwards;
    transform: translateX(200rem);
}
@keyframes side-content-img {
    0%{
        transform: translateX(200rem);
    }
    80%{
        transform: translateX(-5rem);
    }
    100%{
        transform: translateX(0rem);
    }
    
}

/* SIDE CONTENT DIV */
.side-content div{
    width: 100%;
    height: 100%;
    grid-row: 1 / 2;
    grid-column: 4 / 7;
    background: var(--acentuar);
}

/* FOOTER */
.footer{
    display: flex;
    justify-content: space-evenly;
    align-items: flex-end;
    border-radius: 0 8rem 0 0;
    background: var(--acentuar);
}

/* FOOTER - ANIMATION */
.footer{
    animation: 1.5s footer 4s ease-in-out forwards;
    transform: translateY(200rem);
}
@keyframes footer{
    0%{
        transform: translateY(200rem);
    }
    100%{
        transform: translateY(0rem);
    }
    
}
/* FOOTER DIV */
.footer div {
    display: flex;
    justify-content: center;
    width: 12rem;
    height: 50%;
    border-radius: 2rem 2rem 0 0;
    background-color: var(--terciario);
}

/* FOOTER DIV - ANIMATION */
.footer div {
    animation: 1.5s footer-div 5s ease-in-out forwards;
    transform: translateY(200rem);
}
.footer div:nth-child(1){
    animation-delay: 5s;
}
.footer div:nth-child(2){
    animation-delay: 5.3s;
}
.footer div:nth-child(3){
    animation-delay: 5.6s;
}
@keyframes footer-div{
    0%{
        transform: translateY(200rem);
    }
    100%{
        transform: translateY(0rem);
    }
    
}

/* FOOTER IMG */
.footer div img{
    width: 16rem;
    position: absolute;
    bottom: 6.5rem;
    transform: translateY(0rem) scale(1);
    transition: transform 0.5s ease-in-out;
}
.footer div:hover > img{
    cursor: pointer;
    transform: translateY(-2rem) scale(1.3);
    transition: transform 0.3s ease-in-out;
}

/* FOOTER IMG - ANIMATION */
.footer div img{
    animation: 1.5s footer-img 6.5s ease-in-out forwards;
    transform: translateY(200rem);
}
.footer div:nth-child(1) > img{
    animation-delay: 6s;
}
.footer div:nth-child(2) > img{
    animation-delay: 6.3s;
}
.footer div:nth-child(3) > img{
    animation-delay: 6.6s;
}
@keyframes footer-img{
    0%{
        transform: translateY(200rem);
    }
    100%{
        transform: translateY(0rem);
    }
    
}

/* MODAL */
.hidden{
    display: none;
}
.visible{
    display: grid;
}
.modal{
    place-items: center;
    width: 100%;
    height: 100%;
    position: absolute;
    background: rgba(0, 0, 0, 0.5);
    z-index: 3;
}
.modal__content{
    width: 50%;
    height: 50%;
    background:var(--secundario);
}

JS

const modal = document.querySelector('.modal')
const modalContent = document.querySelector('.modal__content')
const openModal = document.querySelectorAll('.openModal')
const closeModal = document.querySelector('.modal__content--close')

openModal.forEach((btnModal) =>
    btnModal.addEventListener('click', () => {
        openModalFunction(modal)
        openModalFunction(modalContent)
    }) 
)

closeModal.addEventListener('click', () => {
    closeModalFunction(modal)
    closeModalFunction(modalContent)
})

const openModalFunction = function  (objModal){
    objModal.classList.remove('hidden')
    objModal.classList.add('visible')
}
const closeModalFunction = function  (objModal){
    objModal.classList.remove('visible')
    objModal.classList.add('hidden')
}

considero que sería más sencillo solo agregar y quitar la clase hidden y así solo en JS agregas y quitas esa clase para no tener que utilizar Important o remove y add en una misma función. 💚

Mi solución con un metodo forEach y querySelectorAll

¿Quién más pensó que sería buena idea que Estefany haga el curso de JS tambien 😍?

Creo que no es necesario el uso del !important en la clase modal, solo es no agregar el parámetro grid y dejarle esa tarea a la clase visible, solo en modal usar el place-items.

Modal: evento click con JavaScript

Código de la clase: https://github.com/paolojoaquin/lego-superheroes/tree/Clase/10-evento-click-con-javascript
Según entendí por @erickgomez (gracias 😄).
1.- NO hace falta poner !important, porque recordemos que es una mala práctica.
2.- Estaba bien como la profesora lo estaba haciendo, el único “error” fue de que inicializamos nuestra clase modal diferente de none osea le decíamos que aparezca en el DOM.
3.- Para arreglar esto debíamos quitar el display:grid o cambiarlo por display:none de nuestra clase .modal , quedando:
Antes:

.modal {
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.5);
    position: absolute;
    z-index: 4;
    display: grid;
    place-items: center;
}

Después:

.modal {
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.5);
    position: absolute;
    z-index: 4;
    /* display: none; */
    place-items: center;
}

no hace falta usar el important con mejorar la especificidad basta.

.modal.hidden{
display: none;
}

.modal.visible{
display: grid;
}

.modal{
width: 100%;
height: 100%;
background: rgba(0,0,0, 0.5);
position: absolute;
z-index: 4;

display: grid;
place-items: center;

}

.modal.visible es cuando elemento tiene la clases “modal visible”

.modal.hidden es cuando el elemento tiene las clases
"modal hidden"

por ejemplo:

.padre .visible aqui estamos seleccionando un elemento con la clase “visible” que se encuentra dentro de otro elemento con la clase “padre”

.padre.visible aqui hacemos referencia a un elemento tiene las clases “padre visible”

Consejo 😃

Actualmente estoy trabajando para un proyecto de factoring como frontend developer y puedo decir que si es mala practica el !Important ,no considero excepciones al respecto , debido a desarrolladores pasados me he topado con código de CSS muy dificil de manipular o directamente inmanipulable esto debido a !important regados por ahí ,recordemos que el código debe ser siempre reusable y escalable ,así que bajo ninguna circunstancia usen !important ,no notaran el problema si no hasta que el proyecto tenga miles y miles de líneas de código en solo estilos

Solucioné de la siguiente manera los eventos de click para cada imagen.

Coloqué una clase a cada div del footer :

<section class="footer">
        <div class="buttonImage" id="button1">
          <img src="https://i.ibb.co/tKWqw8J/spiderman.png" alt="spiderman">
        </div>

        <div class="buttonImage" id="button2">
          <img src="https://i.ibb.co/Xzsdvgg/robin.png" alt="robin">
        </div>

        <div class="buttonImage" id="button3">
          <img src="https://i.ibb.co/M18p91c/batman.webp" alt="batman">
        </div>
      </section>

Y los usé desde Javascript trayendolos con un querySelectorAll(). Lo que me retorna un NodeList para así poder usar el metodo forEach() y recorrerlos escribiendo un solo evento para cada uno.

const modal = document.querySelector('.modal')
const allButtonsImages = document.querySelectorAll('.buttonImage')
const closeButton = document.querySelector('.modal__content--close')

allButtonsImages.forEach(simpleButton => {
  simpleButton.addEventListener('click', ()=>{
    modal.classList.remove('hidden')
    modal.classList.add('visible')
  })
})

closeButton.addEventListener('click', () => {
  modal.classList.remove('visible')
  modal.classList.add('hidden')
})

HTML semantico y accesible:

<section class="modal hidden">
	<div class="modal__content">
		<button
			class="modal__close-btn"
			id="modal__close-btn"
			title="button to close modal"
		></button>
	</div>
</section>

.
Estilos usando Sass:

.
JS

No es necesario el !important adema’s he agregado a que cuando le demos click en el back del modal se cierre también:

HTML

<!-- modal -->
            <section class="modal modal--hidden">
                <div class="modal__content">
                    <img class="modal__content--close" src="https://img.icons8.com/windows/50/000000/macos-close.png" alt="Cerrar Modal">
                </div>
                <div class="back-modal" id="back-modal"></div>
            </section>

CSS

.modal {
    width: 100%;
    height: 100%;
    background-color: rgba(0, 0, 0, 0.5);
    position: fixed;
    z-index: 5;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    display: grid;
    place-items: center;
}

.modal--hidden {
    display: none;
}

.modal--visible {
    display: grid;
}

.modal__content {
    background-color: white;
    width: 50%;
    height: 50%;
    z-index: 7;
    position: relative;
}

.modal .back-modal {
    width: 100%;
    height: 100%;
    position: fixed;
    z-index: 6;
}
.modal .modal__content--close {
    cursor: pointer;
    position: absolute;
    right: 0;
}

JS

const modal = document.querySelector('.modal');
const button1 = document.querySelector('#button1');
const backModal = document.querySelector('#back-modal');
const closeModal = document.querySelector('.modal__content--close');

button1.addEventListener('click', () => {
    modal.classList.remove('modal--hidden');
    modal.classList.add('modal--visible');
});

closeModal.addEventListener('click', () => {
    modal.classList.add('modal--hidden');
    modal.classList.remove('modal--visible');
});

backModal.addEventListener('click', () => {
    modal.classList.add('modal--hidden');
    modal.classList.remove('modal--visible');
});

Buenas comunidad, aquí les dejo una forma diferente de realizar los eventos click sin escribir tantas lineas de código y para todos los buttons.


	<section class="footer">
                <div  onclick="visibleModal()">
                    <img src="https://i.ibb.co/tKWqw8J/spiderman.png" alt="spiderman">
                </div>

                <div  onclick="visibleModal()">
                    <img src="https://i.ibb.co/Xzsdvgg/robin.png" alt="robin">
                </div>

                <div  onclick="visibleModal()">
                    <img src="https://i.ibb.co/M18p91c/batman.webp" alt="batman">
                </div>
            </section>

Y el script es el siguiente

const modal = document.querySelector('.modal');
const buttonClose = document.querySelector('.modal-content-close');

const visibleModal = () => {
    modal.classList.remove('hidden')
    modal.classList.add('visible')
}

buttonClose.addEventListener('click', () => {
    modal.classList.remove('visible')
    modal.classList.add('hidden')
})

Recuerden que el css el navegador procesa los estilos desde el primero hasta el ultimo, si primero colocan los estilos del .hidden y luego .vissible, al momento de renderizar siempre quedaran los del .visible porque es el ultimo en cargar

```css .modal{ width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); position: absolute; z-index: 4; display: grid; place-content: center center; } .hidden{ display:none; } .visible{ display: grid; } .modal-contenedor{ background-color: aliceblue; width: 50rem; height: 40rem; border-radius: 5rem; } .modal-contenedor-close{ width: 5rem; height: 4.5rem; position: relative; left: 2rem; top: 1rem; } ```yo coloque primer lugar .modal y luego .hidden por que tomal lo ultimo que escribimos en una misma clase \<section class="modal hidden"> como los dos escribimos para el mismo elemento en css tomo lo ultimo que escribimos de ese elemento
`modal.classList.toggle('hidden');`
Para no hacer un EventListener para cada boton se puede hacer asi ===>> ```js const modal = document.querySelector('.modal'); const heroes = document.querySelectorAll('#button1'); const close = document.querySelector('.modal-content--close') for (const hero of heroes) { hero.addEventListener('click', () => { modal.classList.remove('hidden'); modal.classList.add('visible'); }) } close.addEventListener('click', () => { modal.classList.remove('visible'); modal.classList.add('hidden'); }) ```

Por si quieres cerrar tu ventana dando click en la parte gris, te dejo el codigo.

<code> 
modal.addEventListener('click',(event)=>{
    if(!modal__content.contains(event.target)){
        modal.classList.remove("visible");
        modal.classList.add("hidden");
    }
})

Si necesitan íconos para sus proyectos, pueden visitar la página de evericons.

Para no crear variables por cada superhéroe, podemos usar el método querySelectorAll y después de ello un forEach para darles el addEventListener.

const modal_triggers = document.querySelectorAll('.footer__images')

modal_triggers.forEach(
  e => e.addEventListener(
    'click', () => modal.classList.remove('hidden')
  )
)

Buenas, así me quedo a mí por si alguien le interesa

JavaScript

const images = document.querySelectorAll('.img');
const modal = document.querySelector('.modal')

images.forEach(image => {
  image.addEventListener("click", () => {
    modal.classList.remove('hidden')
    modal.classList.add('visible')
  })
});

const close = document.querySelector('.icon-close')

close.addEventListener('click', () => {
  modal.classList.remove('visible')
  modal.classList.add('hidden')
})

HTML

<div>
	<img class="img"  src="/public/assets/joker.png" width="160px" alt="image joker lego">
</div>
<div>
	<img class="img" src="../public/assets/deadpool.png" width="200px" alt="image deadpool lego">
</div>
<div>
	<img class="img" src="../public/assets/spiderman.png" width="200px" alt="image spiderman lego">
</div>

Sass

.hidden {
  display: none;
}

.visible {
  display: flex;
  align-items: center;
  justify-content: center;
}

.modal {
  width: 100%;
  height: 100%;
  background-color: rgb(0, 0, 0, 0.5);
  position: absolute;
  z-index: 4;
}

.modal-content {
  position: relative;
  background-color: $color-primary;
  width: 50%;
  height: 50%;

  img {
    position: absolute;
    right: 15px;
    top: 15px;
    width: 35px;
    cursor: pointer;
  }
}

En JavaScript existe un concepto que es muy importante y se pregunta mucho en las entrevistas y es: Event Delegation , es una tecnica que involucra añadir un Event Listener al padre del elemento en lugar del añadirselo a los elementos descendientes.

Lo que hice fue añadirle el evento click al padre .footer__content y con el target logro detectar a cual elemento hijo se le esta haciendo click. Esto me permite que ahorrar mucho codigo y por si algun motivo quiero añadir otro personaje no tengo que añadirle un evento a cada hijo.

// HTML CODE
<section class="footer__content">
  <div>
    <img src="./assets/spiderman.png" alt="spiderman">
  </div>
  <div>
    <img src="./assets/robin.png" alt="robin">
  </div>
  <div>
    <img src="./assets/batman.webp" alt="batman">
  </div>
</section>
// JAVASCRIPT CODE
const button = document.querySelector('.footer__content');
const modal = document.querySelector('.modal');

button.addEventListener('click', handleModal)

function handleModal(e) {
    const element = ['DIV', 'IMG'];
    const target = e.target.tagName;
    
    if (element.includes(target)) {
        modal.classList.remove('hidden');
    }
}

para mas informacion: