Apart of moving my website to Astro from Next.js meant that I could now use Custom Web Components on my site, web components are custom HTML elements that can be extended by developers via Javascript.

For apart of my articles I sometimes have images in them, which might sometimes include text which might be harder at the width of the article. To solve this problem I wanted to add what is called a Image Lightbox, which is a dialog which opens a larger version of the image.

I decided to use a custom web component for this, so it was just a HTML element I wrap around a image element, and can be reused where needed.

Example

Below is an example of the what we are building, clicking the button should open a dialog with a larger version of the image.

placeholder

The code

Basics of Web components

To extend a web component we create a class which extends the HTMLElement base class or a class relating to a HTML standard element such as HTMLParagraphElement.

Inside the code block below we have two methods inside the class, the first being connectedCallback this is called when the custom component is added to the page, this the method where can set up our custom logic. The disconnectedCallback method is opposite and is called when an element is removed from the document.

class ImageLightbox extends HTMLElement {
	constructor() {
		super();
	}

	connectedCallback() {
      console.log('inside connectedCallback');
	}

	disconnectedCallback() {
		console.log('inside disconnectedCallback');
	}
}

customElements.define('custom-lightbox', ImageLightbox);

The customElements define function is what connects the javascript class to the HTML element, the first property we add is the HTML element tag, and the second being the javascript class.

In the HTML we wrap the image element we want to add the lightbox to in the HTML element tag we used in the define method like this:

<custom-lightbox>
    <img src='' alt=''/>
</custom-lightbox>

Lightbox Logic

In the code below I have added the logic to find the image and create the dialog element.

The first part is finding the image element we want to display in the dialog, this is done via querySelector using this object so it only finds elements that are inside the custom element.

Next using vanilla Javascript I create button and the event that triggers opening the dialog and append this to the custom element, clicking this button will open the dialog if one already exists, or if it the first time it is clicked it will create the dialog with the image in and the close dialog button.

class ImageLightbox extends HTMLElement {
	constructor() {
		super();
	}

	connectedCallback() {
		this.findImage();
		this.addDialogButton();
	}

	disconnectedCallback() {
		console.log('inside disconnectedCallback');
	}

	findImage() {
		this.image = this.querySelector('img');
	}

	addDialogButton() {
		const dialogButton = document.createElement('button');
		dialogButton.classList.add('open-lightbox');
		dialogButton.textContent = 'Open Lightbox';
		dialogButton.addEventListener('click', this.openDialog.bind(this));
		this.appendChild(dialogButton);
	}

	addDialogElement() {
		const dialog = document.createElement('dialog');
		dialog.innerHTML = `<img src='${this.image?.src}' alt='${this.image?.alt}' />`;
		this.appendChild(dialog);
		this.dialog = dialog;
		this.dialogCloseButton();
	}

	openDialog() {
		if (!this.dialog) {
			this.addDialogElement();
		}
		this.dialog?.showModal();
	}

	dialogCloseButton() {
		const closeButton = document.createElement('button');
		closeButton.textContent = 'Close Lightbox';
		closeButton.addEventListener('click', this.closeDialog.bind(this));
		this.dialog?.appendChild(closeButton);
	}

	closeDialog() {
		const dialog = this.querySelector('dialog');
		dialog?.close();
	}
}

customElements.define('custom-lightbox', ImageLightbox);

How I could extend it further

This is only a basic implementation of a lightbox image component, which I could expand on the existing logic in this article to add features like pinch to zoom for mobile devices allowing mobile to uses to easier see the contents of the image, change the search params on the image to load a large and better quality image in the dialog and allow slideshows if multiple images for example a product listing images.