Skip to content

🎨 Optimized image placeholders with BlurHash for Kirby (3 & 4)

License

Notifications You must be signed in to change notification settings

tobimori/kirby-blurhash

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Kirby BlurHash Banner

Kirby BlurHash

BlurHash is an optimized image placeholder algorithm, developed at Wolt. Placeholders are represented by small ∼20-50 bytes hashes, instead of larger (∼1kB+) base64-encoded images.

This plugin adds BlurHash support to Kirby 3, allowing you to implement UX improvements such as progressive image loading or content-aware spoiler images like Mastodon.

Under the hood, the heavy work gets done by a PHP implementation of BlurHash by kornrunner: kornrunner/php-blurhash

Be aware that BlurHash currently has no support for transparency, so it will be rendered in black.

Requirements

Installation

Download

Download and copy this repository to /site/plugins/kirby-blurhash.

Composer

composer require tobimori/kirby-blurhash

Usage

Client-side decoding

The default implementation of BlurHash expects the string to be decoded on the client-side, using a library like Wolt's blurhash or fast-blurhash.

This provides the most benefits, most notably including better color representation and smaller payload size, but requires the initial execution of such a library on the client-side, and thus is better used with a headless site, a site that features many, high quality images or heavily makes use of client-side infinite scrolling/loading.

$file->blurhash() encodes the image with BlurHash and returns it as a string

An example implementation generating a placeholder image using the BlurHash string could look like this:

<div
  data-blurhash="<?= $image->blurhash() ?>" // BlurHash string as attribute, to access via JS
  style="aspect-ratio: <?= $image->ratio() ?>;"> // Aspect ratio is required as canvas is absolutely positioned
</div>
import { decodeBlurHash } from 'fast-blurhash' // https://github.com/mad-gooze/fast-blurhash

const el = document.querySelector('div[data-blurhash]')
if (!el) return

const { blurhash } = el.dataset
if (!blurhash) return

const pixels = decodeBlurHash(blurhash, 32, 32)

const canvas = document.createElement('canvas')
canvas.width = 32
canvas.height = 32

const ctx = canvas.getContext('2d')
if (!ctx) return
const imageData = ctx.createImageData(32, 32)
imageData.data.set(pixels)
ctx.putImageData(imageData, 0, 0)
el.appendChild(canvas)
div {
  position: relative;
  width: 400px;
}

canvas {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  height: 100%;
  width: 100%;
}

Details will vary in your implementation, as this e.g. does not feature lazy-loading capabilities, or you might want to use a different library, but the general idea is to use the BlurHash string as an attribute on an element, and then decode the BlurHash string on the client-side.

Server-side decoding

In addition to simply outputting the BlurHash string for usage on the client-side, this plugin also provides a server-side decoding option that allows you to output a base64-encoded image string, which can be used as a placeholder image without any client-side libraries, similar to Kirby Blurry Placeholder.

This is especially useful when you only have a few images on your site or don't want to go through the hassle of using a client-side library for outputting placeholders. Using this approach, you'll still get better color representation of the BlurHash algorithm than with regularly downsizing an image, but image previews will still be about ~1kB large.

$file->blurhashUri() encodes the image with BlurHash, decodes & rasterizes it, finally returns it as a data URI which can be used inline in a src attribute.

<img src="<?= $image->blurhashUri() ?>" />

With an lazy-loading library like vanilla-lazyload (supports everything) or Loadeer.js (smaller/faster, doesn't support iframes, videos or background images), your implementation could look like this:

<img
  src="<?= $image->blurhashUri() ?>"
  data-src="<?= $image->url() ?>" // Original src attribute that will be replaced by the lazy-loading library
  data-lazyload // Attribute for browser to know what to lazy-load
  alt="<?= $image->alt() ?>"
/>
import LazyLoad from 'vanilla-lazyload'

const lazy = new LazyLoad({ elements_selector: '[data-lazyload]' })

Cropped images

Kirby doesn't support file methods on cropped images, so you'll have to use the original image, and pass the ratio as attribute to the element to get the correct BlurHash.

<?php $cropped = $original->crop(500, 400) ?>
<img
  src="<?= $original->blurhashUri(5/4) ?>"
  data-src="<?= $cropped->url() ?>"
  data-lazyload
  alt="<?= $original->alt() ?>"
/>

This is also supported by $file->blurhash($ratio).

Options

Option Default Description
cache.decode true Enable decoding cache
cache.encode true Enable encoding cache
sampleMaxSize 100 Max width or height for smaller image that gets encoded (watch out for memory)
componentsTarget 12 Max number of components ("blur points") for encoding (x * y <= ~x)
decodeTarget 100 Pixel Target (width * height = ~P) for decoding

Options allow you to fine tune the behaviour of the plugin. You can set them in your config.php file:

return [
    'tobimori.blurhash' => [
        'sampleMaxSize' => 200,
        'componentsTarget' => 12,
        'decodeTarget' => 100,
    ],
];

Comparison

Comparison image Comparison image Comparison image

Credits

License

MIT License Copyright © 2023 Tobias Möritz