Back

zoomable images

tjheffner

2024-01-21


I wanted a simple way to zoom in on images included in gallery pages, like so:

Here’s how I’m doing that with a little bit of CSS and some checkbox logic. By nesting the input and image inside the label, the whole thing is clickable for a simple zoom effect. And thanks to the checkbox, each image’s zoom state is independent of all other images on the page.

<div class="zoom">
  <label>
    <input type="checkbox">
    <img src="..." alt="..." loading="lazy" />
  </label>
</div>
/* zoomable images */
.zoom input[type=checkbox] {
  display: none
}

.zoom img {
  transition: transform 0.25s ease;
  cursor: zoom-in
}

.zoom input[type=checkbox]:checked~img {
  transform: scale(1.65);
  cursor: zoom-out
}

Pretty nifty! The only issue now is that I store all of my images in Github issues. How can i make a markdown image (![alt](src)) into the html structure that I need?

Thanks to the swyxkit base, this site is already set up with mdsvex, remark and rehype. Because we are interested in the HTML structure for this particular problem, we specifically want a plugin for rehype.

It was simple to write my own plugin to create the structure I needed. Also, major credit to this comment which got me 9/10ths of the way there.

Anyway, here is the plugin:

import { visit } from 'unist-util-visit'
import { selectAll } from 'hast-util-select'
import { fromSelector } from 'hast-util-from-selector'

export default function rehypeZoomImages(options = { selector: 'img' }) {
  return (tree) => {
    for (const match of selectAll(options.selector, tree)) {
      visit(tree, match, (node, i, parent) => {
        const wrapper = fromSelector('div.zoom ')
        const label = fromSelector('label')
        const checkbox = fromSelector('input[type="checkbox"]')

        node.properties.loading = "lazy"

        label.children = [checkbox, node]
        wrapper.children = [label]
        parent.children[i] = wrapper
      })
    }
  }
}

When given an html AST (the “hast” in the dependencies above), we have a very structured tree to search through. Markdown from a Github issue is fed through mdsvex using my configured remark & rehype plugins. Slotting the above plugin into the list means all <img> selectors present in the tree will be wrapped with my desired html structure for zoomable images. A <div> that contains a <label>, and that label is the parent of the checkbox <input> that controls state, plus the original <img /> node that was found. While i’m here, I also add the loading="lazy" attribute to the img node because my gallery pages tend to be quite image heavy.

Here’s a great primer on how ASTs work for HTML if you want to know more.


Loading comments...
Back to top