initial: support lqip and responsive image rendering/resizing
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
_vendor/
|
||||
resources/
|
||||
public/
|
||||
@@ -0,0 +1,81 @@
|
||||
<!-- <a href=""><img alt="Logo" align="right" width="200" src="https://raw.githubusercontent.com/hugo-mods/lazy/main/.github/logo.png"></a> -->
|
||||
|
||||
# lazyimg
|
||||
## Lazy image loading made easy. With automatic image resizing and LQIP support.
|
||||
|
||||
- Lazy loading for images via `lazySizes`
|
||||
- No-Script-safe: Fallback to browser's native method
|
||||
- ...
|
||||
|
||||
> Used by the [Osprey Delight](https://github.com/kdevo/osprey-delight) theme, which directly benefits from this module!
|
||||
|
||||
## Go get it
|
||||
|
||||
Initialize [Hugo's mod system](https://gohugo.io/hugo-modules/) on your site:
|
||||
|
||||
`hugo mod init github.com/{username}/{repo}`
|
||||
|
||||
Add module to site's config (e.g. `config.yaml`):
|
||||
|
||||
```yaml
|
||||
module:
|
||||
imports:
|
||||
- path: github.com/hugo-mods/lazyimg
|
||||
```
|
||||
|
||||
Get the module (also upgrades existing one):
|
||||
|
||||
`hugo mod get -u`
|
||||
|
||||
## Quickstart
|
||||
|
||||
1. Put the images which you want to use in the `assets` directory of your project. They should be in a high resolution and will be resized automatically.
|
||||
2. Add the following boilerplate setup code to your site's `<head>`:
|
||||
```
|
||||
{{ partial "lazyimg-setup" }}
|
||||
```
|
||||
3. Load the image by calling the `lazyimg` partial where you would usually use an `img` tag:
|
||||
```
|
||||
{{ partial "lazyimg" "my-awesome-image.jpeg" }}
|
||||
```
|
||||
|
||||
For more advanced usage, please refer to the [`exampleSite`](./exampleSite) for a practical approach or continue reading the theory as described in the next section.
|
||||
|
||||
## Configuration
|
||||
|
||||
### Resizers
|
||||
|
||||
| Name | Description
|
||||
|:----------------------------|-------------------------------------------------------------
|
||||
| `simple` | Produces default image with `maxSize`, LQIP with `lqipSize`.
|
||||
| `responsive` | Produces default image with `maxSize`, LQIP with `lqipSize` and responsive images based on `responsiveSizes`.
|
||||
| `auto` | Produces default image with `maxSize`, LQIP with `lqipSize` and guesses responsive sizes based on an algorithm. Highly experimental.
|
||||
|
||||
### Renderers
|
||||
|
||||
| Name | Description
|
||||
|:----------------------------|-------------------------------------------------------------
|
||||
| `lqip` | All-around responsive lazy-loading with LQIP blur-up preview. Recommended: [`lazyimg.css`](#CSS).
|
||||
| `lqip-webp` | `lqip` with additional WebP support. TODO(kdevo): not implemented yet.
|
||||
|
||||
### CSS
|
||||
|
||||
The `lazyimg.css` is the recommended boilderplate CSS.
|
||||
It contains rules for blur-up animation, hiding "broken image icon" on lazy-loading and [no-js](#disabled-js) selector.
|
||||
|
||||
### Disabled JS
|
||||
|
||||
Support for disabled JS can be accomplished by adding a "no-js" class to your `html` root tag, e.g.:
|
||||
|
||||
```html
|
||||
<html lang="en" class="no-js"> <!-- ... --> </html>
|
||||
```
|
||||
|
||||
Then, to remove the class when the client indeed supports JS, add the following script:
|
||||
|
||||
```html
|
||||
<script>document.documentElement.className = "js"</script>
|
||||
```
|
||||
|
||||
Alternatively, use `lazyimg-setup-nojs` instead of `lazyimg-setup` which will do the replacement for you.
|
||||
In this case, you only need to add the `no-js` class to your `html` root tag.
|
||||
@@ -0,0 +1,20 @@
|
||||
/* Hides images to be loaded with JS when it is disabled */
|
||||
.no-js img.lazyload {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Hides broken image icon which may shortly be disaplayed on load */
|
||||
img.lazyload:not([src]) {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
/* Simple blur-up animation */
|
||||
.blur-up {
|
||||
-webkit-filter: blur(3px);
|
||||
filter: blur(3px);
|
||||
transition: filter 500ms, -webkit-filter 500ms;
|
||||
}
|
||||
.blur-up.lazyloaded {
|
||||
-webkit-filter: blur(0);
|
||||
filter: blur(0);
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
Params:
|
||||
lazyimg:
|
||||
# TODO(kdevo): provide sensible defaults
|
||||
|
||||
module:
|
||||
hugoVersion:
|
||||
extended: true
|
||||
# needed for webp support and mutli-line dicts:
|
||||
min: "0.82.0" # TODO(kdevo): raise to 0.83.0
|
||||
max: "1.00.0"
|
||||
imports:
|
||||
- path: "github.com/aFarkas/lazysizes"
|
||||
mounts:
|
||||
- source: "./"
|
||||
target: "assets/lazyimg/lazysizes"
|
||||
@@ -0,0 +1,3 @@
|
||||
_vendor/
|
||||
resources/
|
||||
public/
|
||||
@@ -0,0 +1,25 @@
|
||||
module:
|
||||
imports:
|
||||
- path: github.com/hugo-mods/lazyimg
|
||||
# replacements forces using local version for dev instead of using the remote:
|
||||
replacements:
|
||||
- github.com/hugo-mods/lazyimg -> ../../.
|
||||
|
||||
# disableKinds is only needed for this exampleSite to surpress some warnings:
|
||||
disableKinds: ["taxonomy", "term"]
|
||||
|
||||
Params:
|
||||
lazyimg:
|
||||
resizer: auto
|
||||
renderer: lqip
|
||||
|
||||
# Resizer options:
|
||||
lqipSize: "120x"
|
||||
maxSize: "1920x"
|
||||
responsiveSizes: ["320x", "640x", "768x", "1024x", "1366x", "1600x", "1920x"]
|
||||
resizeOptions: "Lanczos q95"
|
||||
|
||||
# Renderer options:
|
||||
class: "my-img-class"
|
||||
alt: "My logo"
|
||||
noscript: true
|
||||
@@ -0,0 +1,3 @@
|
||||
module github.com/hugo-mods/lazyimg/exampleSite
|
||||
|
||||
go 1.13
|
||||
@@ -0,0 +1,55 @@
|
||||
<!doctype html>
|
||||
<!-- The 'no-js' class will be removed on lazyimg-setup: -->
|
||||
<html lang="en" class="no-js">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>hugo-mods: lazyimg</title>
|
||||
<style>
|
||||
/* TODO(kdevo): create a nice flexbox image gallery example
|
||||
/* .grid {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.item {
|
||||
width: 50%;
|
||||
flex-grow: 1;
|
||||
height: auto;
|
||||
max-width: 1024px;
|
||||
} */
|
||||
html {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
img {
|
||||
height: auto;
|
||||
width: 100%;
|
||||
max-width: 1920px;
|
||||
|
||||
border-radius: 5px;
|
||||
border: 1px solid #aaa;
|
||||
}
|
||||
</style>
|
||||
<!-- lazyimg.css ensures that the image is not displayed twice when the user has no-js: -->
|
||||
<style>{{ (resources.Get "lazyimg.css" | minify).Content | safeCSS }}</style>
|
||||
|
||||
<!-- This script basically loads laszysizes asynchronously: -->
|
||||
{{ partial "lazyimg-setup-nojs" . }}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- Most basic usage with single parameter that can either be a filename or resource: -->
|
||||
{{ partial "lazyimg" "alps.jpg" }}
|
||||
|
||||
<!-- Here we call the partial by passing a param dict that overrides the site's params: -->
|
||||
{{ partial "lazyimg" (dict "img" "alps.jpg" "class" "my-fancy-class" "alt" "This image shows the alps" "resizer" "simple") }}
|
||||
|
||||
{{ $images := (partial "images" .) }}
|
||||
{{ range $images -}}
|
||||
<!-- Here we call the partial with a resource: -->
|
||||
{{ partial "lazyimg" . }}
|
||||
{{- end }}
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,7 @@
|
||||
<!-- Create different versions of example image to demonstrate lazy-loading: -->
|
||||
{{ $images := slice }}
|
||||
{{ range seq 10 -}}
|
||||
{{ $img := resources.Get "alps.jpg" | images.Filter (images.Hue (add -180 (mul . 36))) }}
|
||||
{{ $images = append $img $images }}
|
||||
{{- end }}
|
||||
{{ return $images }}
|
||||
@@ -0,0 +1,5 @@
|
||||
module github.com/kdevo/hugo-mod-lazy
|
||||
|
||||
go 1.13
|
||||
|
||||
require github.com/aFarkas/lazysizes v0.0.0-20210408151554-27c7ceb7d737 // indirect
|
||||
@@ -0,0 +1,2 @@
|
||||
github.com/aFarkas/lazysizes v0.0.0-20210408151554-27c7ceb7d737 h1:0rEJGkemFAVemGpxArHHkICg/XhP6parOV4vnnjT174=
|
||||
github.com/aFarkas/lazysizes v0.0.0-20210408151554-27c7ceb7d737/go.mod h1:xl3heUINAn0oPowV1biDQ2Pyw1w3x66I7KEbWkaxox0=
|
||||
@@ -0,0 +1,2 @@
|
||||
{{ partial "partials/lazyimg-setup" }}
|
||||
<script>document.documentElement.className = document.documentElement.className.replace(/\bno-js\b/g, "js");</script>
|
||||
@@ -0,0 +1,6 @@
|
||||
{{- $lsPath := "lazyimg/lazysizes/lazysizes.min.js" -}}
|
||||
{{- with (resources.Get $lsPath) -}}
|
||||
<script src="{{ .Permalink }}" async></script>
|
||||
{{- else -}}
|
||||
{{ errorf "could not locate %s" $lsPath }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,32 @@
|
||||
{{/* Parameter Parsing */}}
|
||||
{{ $usage := `lazyimg:
|
||||
Usage like:
|
||||
- {{ partial "lazyimg" "image.jpg" }}
|
||||
- {{ partial "lazyimg" (resources.Get "image.jpg") }}
|
||||
|
||||
...or more verbose by specifying a dict overriding the site.Params.lazyimg, e.g.:
|
||||
- {{ partial "lazyimg" (dict "resizer" "auto" "renderer" "lqip") }}
|
||||
- {{ partial "lazyimg" (dict "img" "image.jpg" "responsiveSizes" (slice "320px" "640px") "resizer" "responsive") }}
|
||||
|
||||
Please ensure that image exists in ./assets directory.
|
||||
` }}
|
||||
|
||||
{{ $params := merge site.Params.lazyimg (dict
|
||||
"img" .
|
||||
"resizer" (default "responsive" site.Params.lazyimg.resizer)
|
||||
"renderer" (default "lqip" site.Params.lazyimg.renderer)
|
||||
)}}
|
||||
{{ if reflect.IsMap . }}
|
||||
{{ $params = merge $params . }}
|
||||
{{ end }}
|
||||
{{ $resType := (printf "%T" $params.img) }}
|
||||
{{ if not (in $resType "resource") }}
|
||||
{{ $params = merge $params (dict "img" (resources.Get $params.img)) }}
|
||||
{{ end }}
|
||||
{{ if not $params.img }}
|
||||
{{ errorf "image path ./assets/%s does not exist" $params.img }}
|
||||
{{ warnf $usage }}
|
||||
{{ end }}
|
||||
|
||||
{{ $sizes := partial (printf "resizer/%s" $params.resizer) $params }}
|
||||
{{ return (partial (printf "renderer/%s" $params.renderer) (merge (dict "imgDict" $sizes) $params)) }}
|
||||
@@ -0,0 +1,7 @@
|
||||
<picture>
|
||||
<!-- TODO(kdevo): load webp images -->
|
||||
<source type="image/webp"
|
||||
srcset="
|
||||
URL 100w,
|
||||
URL 200w">
|
||||
</picture>
|
||||
@@ -0,0 +1,8 @@
|
||||
{{/* Input: Input: dict {"responsive": [RESOURCE, ...]}. Output: srcset string */}}
|
||||
|
||||
{{ $images := .responsive }}
|
||||
{{ $srcset := slice }}
|
||||
{{ range $images }}
|
||||
{{ $srcset = append (printf "%s %dw" .Permalink .Width) $srcset }}
|
||||
{{ end }}
|
||||
{{ return (delimit $srcset ",\n") }}
|
||||
@@ -0,0 +1,28 @@
|
||||
{{ $class := .class }}
|
||||
{{ $alt := .alt }}
|
||||
{{ $noscriptSupport := .noscript }}
|
||||
{{ $imgDict := .imgDict }}
|
||||
{{ if $noscriptSupport }}
|
||||
<noscript>
|
||||
<!-- TODO: add responsiveness -->
|
||||
<img src="{{ $imgDict.default.Permalink }}" {{ with $alt }} alt="{{ . }}" {{ end -}}
|
||||
class="{{ $class }}" loading="lazy"
|
||||
{{- with $imgDict.default }} height="{{ .Height }}" width="{{ .Width }}" {{ end -}}>
|
||||
</noscript>
|
||||
{{ end }}
|
||||
<img
|
||||
{{- with $imgDict.lqip -}}
|
||||
{{- $placeholderB64 := .Content | base64Encode -}}
|
||||
{{- if gt (len $placeholderB64) 2048 }}
|
||||
src="{{ .Permalink }}"
|
||||
{{- else }}
|
||||
src="data:{{ .MediaType }};base64,{{ $placeholderB64 }}"
|
||||
{{- end }}
|
||||
data-src="{{ $imgDict.default.Permalink }}"
|
||||
class="{{ with $class }}{{ . }} {{ end }}blur-up lazyload"
|
||||
{{ else }}
|
||||
src="{{ $imgDict.default.Permalink }}" class="{{ $class }} lazyload"
|
||||
{{- end -}}
|
||||
{{- with (default $imgDict.default $imgDict.lqip) }} height="{{ .Height }}" width="{{ .Width }}" {{- end -}}
|
||||
{{- if $imgDict.responsive -}}{{ with partial "renderer/_srcset" $imgDict }} data-sizes="auto" data-srcset="{{ . }}" {{- end }}{{- end -}}
|
||||
{{- with $alt }} alt="{{ . }}" {{- end }}>
|
||||
@@ -0,0 +1,35 @@
|
||||
{{/* Input: Params. Output: dict {"default": RESOURCE, "lqip": RESOURCE, "responsive": [RESOURCE, ...]} */}}
|
||||
|
||||
{{ $res := .img }}
|
||||
{{ $resizeOptions := .resizeoptions }}
|
||||
{{ $sizes := .responsivesizes }}
|
||||
|
||||
{{ $imageDict := partial "resizer/simple" . }}
|
||||
{{ $default := $imageDict.default }}
|
||||
{{ $name := replace (path.Base ($res.Permalink)) (path.Ext $res.Name) "" }}
|
||||
|
||||
{{/* Responsive img sizing from LQIP width to default width */}}
|
||||
{{ $startWidth := (default 100 $imageDict.lqip.Width) }}
|
||||
{{ $sizes := seq $startWidth 50 $imageDict.default.Width }}
|
||||
{{ $n := 5 }}
|
||||
{{ if gt $n (len $sizes) -}}
|
||||
{{ $n = len $sizes }}
|
||||
{{- end }}
|
||||
{{/* Partition the sizes */}}
|
||||
{{ $part := int (div (len $sizes) $n) }}
|
||||
{{ $wset := slice }}
|
||||
{{ range (seq 1 (sub $n 1)) -}}
|
||||
{{ $wset = append (index $sizes (mul $part .)) $wset }}
|
||||
{{- end }}
|
||||
{{ $wset = (uniq (append (int $imageDict.default.Width) $wset)) }}
|
||||
|
||||
{{/* Fill responsive slice: */}}
|
||||
{{ $responsive := slice }}
|
||||
{{ $break := false }}
|
||||
{{- range $wset -}}
|
||||
{{- $resized := $res.Resize (printf "%dx %s" . $resizeOptions) -}}
|
||||
{{- $resized := slice $resized | resources.Concat (printf "%s-%dx%s" $name . (path.Ext $default.Permalink)) -}}
|
||||
{{- $responsive = append $resized $responsive -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- return merge (dict "responsive" $responsive) $imageDict -}}
|
||||
@@ -0,0 +1,26 @@
|
||||
{{/* Input: Params. Output: dict {"default": RESOURCE, "lqip": RESOURCE, "responsive": [RESOURCE, ...]} */}}
|
||||
|
||||
{{ $res := .img }}
|
||||
{{ $resizeOptions := .resizeoptions }}
|
||||
{{ $sizes := .responsivesizes }}
|
||||
|
||||
{{ $imageDict := partial "resizer/simple" . }}
|
||||
{{ $default := $imageDict.default }}
|
||||
{{ $name := replace (path.Base ($res.Permalink)) (path.Ext $res.Name) "" }}
|
||||
|
||||
{{/* Fill responsive slice. Abort loop with $break when size exceeded (needed due to templating restrictions). */}}
|
||||
{{ $responsive := slice }}
|
||||
{{ $break := false }}
|
||||
{{- range $sizes -}}
|
||||
{{ if not $break }}
|
||||
{{- $resized := $res.Resize (printf "%s %s" . $resizeOptions) -}}
|
||||
{{- $resized := slice $resized | resources.Concat (printf "%s-%s%s" $name . (path.Ext $default.Permalink)) -}}
|
||||
{{- $responsive = append $resized $responsive -}}
|
||||
|
||||
{{- if or (gt $resized.Width $default.Width) (gt $resized.Height $default.Height) -}}
|
||||
{{- $break = true -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- return merge (dict "responsive" $responsive) $imageDict -}}
|
||||
@@ -0,0 +1,16 @@
|
||||
{{/* Input: Params. Output: dict {"default": RESOURCE, "lqip": RESOURCE} */}}
|
||||
|
||||
{{ $res := .img }}
|
||||
{{ $maxSize := .maxsize }}
|
||||
{{ $lqipSize := .lqipsize }}
|
||||
{{ $resizeOptions := .resizeoptions }}
|
||||
|
||||
{{ $name := replace (path.Base ($res.Permalink)) (path.Ext $res.Name) "" }}
|
||||
|
||||
{{/* Resize base images */}}
|
||||
{{ $lqip := $res.Resize (printf "%s %s" $lqipSize $resizeOptions ) }}
|
||||
{{ $lqip := slice $lqip | resources.Concat (printf "%s-%s%s" $name $lqipSize (path.Ext $lqip.Permalink)) }}
|
||||
{{ $default := $res.Resize (printf "%s %s" $maxSize $resizeOptions) }}
|
||||
{{ $default := slice $default | resources.Concat (printf "%s-%s%s" $name $maxSize (path.Ext $default.Permalink)) }}
|
||||
|
||||
{{ return dict "lqip" $lqip "default" $default }}
|
||||
@@ -0,0 +1 @@
|
||||
{{/* TODO(kdevo) */}}
|
||||
Reference in New Issue
Block a user