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