Compare commits
9 Commits
51e757b527
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| d0fe8315cb | |||
| 621d5e5ab2 | |||
| 437b0202ba | |||
|
0e16b03230
|
|||
|
75c0cf2fc3
|
|||
|
2c6026f976
|
|||
|
676c7adfc1
|
|||
|
824e062cfd
|
|||
|
81ece5842c
|
143
README.md
143
README.md
@@ -1,108 +1,89 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2019 Li-Wen Yip <liwen.yip@gmail.com>
|
||||
SPDX-FileCopyrightText: 2019 Max Mehl <mail@mehl.mx>
|
||||
SPDX-FileCopyrightText: 2020 Max Mehl <mail@mehl.mx>
|
||||
SPDX-License-Identifier: MIT
|
||||
-->
|
||||
|
||||
# hugo-snap-gallery
|
||||
|
||||
Automagical css image gallery in [Hugo](https://gohugo.io/) using shortcodes.
|
||||
Automagical css image gallery in [Hugo](https://gohugo.io/) using shortcodes. Lightweight, slim and fully local JavaScript.
|
||||
|
||||
This is a fork of [easy gallery](https://github.com/liwenyip/hugo-easy-gallery). It is supposed to come without any JavaScript and therefore has limited features, but new JS-less stuff is planned.
|
||||
## Features
|
||||
|
||||
## Image Gallery Features
|
||||
- Custom `{{< snap-gallery >}}` shortcode that allows to display multiple images
|
||||
- Two modes:
|
||||
- **slideshow** displaying only one image at a time with the ability to navigate
|
||||
- **gallery** displaying all selected pictures next to each other
|
||||
- All pictures can be expanded on click in a lightbox
|
||||
- Manually select the images you want to display, or provide the path to a directory to use all images inside. This can be combined!
|
||||
- Next/prev buttons in slideshow and lightbox views
|
||||
- The gallery is responsive, images are scaled/cropped to fill 16:10 tiles
|
||||
- CSS and JS is automatically loaded the first time you use the `{{< snap-gallery >}}` shortcode on each page
|
||||
- Multiple galleries/slideshows per page supported, no interference
|
||||
- Automatic rotation of slideshow with a configurable interval. Can also be disabled.
|
||||
- Supports providing metadata such as `alt` and `title` attributes as well as captions
|
||||
|
||||
- Custom `{{< figure >}}` shortcode that enables new features but is backwards-compatible with Hugo's built-in `{{< figure >}}`shortcode
|
||||
- Use the `{{< figure >}}` shortcode by itself to enable pretty captions
|
||||
- Put multiple `{{< figure >}}` shortcodes inside a `{{< gallery >}}` to create a pretty image gallery
|
||||
- **Point `{{< gallery >}}` at a directory to generate a gallery of all images in that directory**
|
||||
- Gallery is responsive, images are scaled/cropped to fill square (or other evenly-sized) tiles
|
||||
- Pretty captions appear/slide/fade upon hovering over the image
|
||||
- Only requires 3.8kB of CSS (unminified; you can minify it if you want)
|
||||
- CSS is automatically loaded the first time you use the `{{< figure >}}` shortcode on each page
|
||||
|
||||
## PhotoSwipe Features
|
||||
|
||||
- Load PhotoSwipe by calling the `{{< load-photoswipe >}}` shortcode anywhere in your post
|
||||
- Loads all of the `<figure>` elements in your post, regardless of where in your post they appear, into a lightbox/carousel style image gallery
|
||||
- Works with any existing `<figure>` elements/shortcodes in your posts
|
||||
- Does not require you to [pre-define the image sizes](http://photoswipe.com/documentation/faq.html#image-size) (the initialisation script pre-loads the image to determine its size; you can optionally pre-define the image size if you want to avoid this pre-loading)
|
||||
|
||||
## Installation
|
||||
|
||||
Use this like an additional Hugo theme.
|
||||
|
||||
|
||||
## `{{< figure >}}` shortcode usage
|
||||
|
||||
Specifying your image files:
|
||||
|
||||
- `{{< figure src="thumb.jpg" link="image.jpg" >}}` will use `thumb.jpg` for thumbnail and `image.jpg` for lightbox
|
||||
- `{{< figure src="image.jpg" >}}` or `{{< figure link="image.jpg" >}}` will use `image.jpg` for both thumbnail and lightbox
|
||||
- `{{< figure link="image.jpg" thumb="-small" >}}` will use `image-small.jpg` for thumbnail and `image.jpg` for lightbox
|
||||
|
||||
Optional parameters:
|
||||
|
||||
- All the [features/parameters](https://gohugo.io/extras/shortcodes) of Hugo's built-in `figure` shortcode work as normal, i.e. src, link, title, caption, class, attr (attribution), attrlink, alt
|
||||
- `size` (e.g. `size="1024x768"`) pre-defines the image size for PhotoSwipe. Use this option if you don't want to pre-load the linked image to determine its size.
|
||||
- `class` allows you to set any custom classes you want on the `<figure>` tag.
|
||||
|
||||
Optional parameters for standalone `{{< figure >}}` shortcodes only (i.e. don't use on `{{< figure >}}` inside `{{< gallery >}}` - strange things may happen if you do):
|
||||
|
||||
- `caption-position` and `caption-effect` work the same as for the `{{< gallery >}}` shortcode (see below).
|
||||
- `width` defines the [`max-width`](https://www.w3schools.com/cssref/pr_dim_max-width.asp) of the image displayed on the page. If using a thumbnail for a standalone figure, set this equal to your thumbnail's native width to make the captions behave properly (or feel free to come up with a better solution and submit a pull request :-)). Also use this option if you don't have a thumbnail and you don't want the hi-res image to take up the entire width of the screen/container.
|
||||
|
||||
|
||||
## `{{< gallery >}}` shortcode usage
|
||||
|
||||
To specify a directory of image files:
|
||||
Use this like an additional Hugo theme, so add it to the `theme` config. Example:
|
||||
|
||||
```
|
||||
{{< gallery dir="/img/your-directory-of-images/" />}}`
|
||||
theme = [ "hugo-sustain", "hugo-snap-gallery" ]
|
||||
```
|
||||
|
||||
- The images are automatically captioned with the file name.
|
||||
- `[image].jpg` is used for the hi-res image, and `[image]-thumb.jpg` is used for the thumbnails.
|
||||
- If `[image]-thumb.jpg` doesn't exist, then `[image].jpg` will be used for both hi-res and thumbnail images.
|
||||
- The default thumbnail suffix is `-thumb`, but you can specify a different one e.g. `thumb="-small"` or `thumb="_150x150"`.
|
||||
## `{{< snap-gallery >}}` shortcode usage
|
||||
|
||||
To specify individual image files:
|
||||
Quickstart:
|
||||
|
||||
```
|
||||
{{< gallery >}}
|
||||
{{< figure src="image1.jpg" >}}
|
||||
{{< figure src="image2.jpg" >}}
|
||||
{{< figure src="image3.jpg" >}}
|
||||
{{< /gallery >}}
|
||||
- `{{< snap-gallery src="image1.jpg, image2.png" >}}`: Display these two images in **gallery** mode
|
||||
- `{{< snap-gallery src="image1.jpg, image2.png" mode="slideshow" >}}`: Display these two images in **slideshow** mode
|
||||
- `{{< snap-gallery src="img/folder1/, image2.png" >}}`: Display all images in the directory `img/folder1` and the single image `image2.png` in **gallery** mode
|
||||
|
||||
All parameters:
|
||||
|
||||
- `src`: Must contain either a comma-separated list of paths to images, or a directory path containing images. Note that the paths are absolute, so imagine a `/` in front of them. Also note that the shortcode assumes that they are all stored in `/static/`.
|
||||
- `lightbox`: Whether a click on an image shall open a lightbox modal. Default: `true`.
|
||||
- `aspectratio`: Define the aspect ratio of the images in the slideshow/gallery. Default: `16/10`.
|
||||
- `metadata`: See below for how to add metadata to your files. Default: `map[]`.
|
||||
- `mode`: Can be either `gallery` or `slideshow`. Default: `gallery`.
|
||||
- For gallery mode:
|
||||
- `columns`: Amount of columns the images are displayed in. Default: `4`.
|
||||
- `minwidth`: Minimum width that each image shall have, e.g. `150px` or `30%`. May conflict with the desired amount of columns. Default: `200px`.
|
||||
- For slideshow mode:
|
||||
- `slideshowwidth`: Width of slideshow, e.g. `300px` or `80%`. Default: `100%`.
|
||||
- `slideshowrotate`: Whether the slideshow shall automatically rotate through the images. Default: `true`.
|
||||
- `slideshowrotate_timer`: Interval of automatic slideshow rotation (if enabled), in milliseconds. Default: `5000` (5 seconds).
|
||||
|
||||
**Note: Boolean values (`true`/`false`) must be provided without surrounding `"` characters!** `lightbox=false` disables the lightbox, while `lightbox="false"` does not.
|
||||
|
||||
### Metadata
|
||||
|
||||
Using separate data files, you can provide metadata to the image files. Imagine using the following shortcode: `{{< snap-gallery src="image1.jpg, img/folder1/" metadata="images.en" >}}`.
|
||||
|
||||
This would assume you have a file named `/data/images.en.yaml`. It may contain the following data:
|
||||
|
||||
```yaml
|
||||
- src: image1.jpg
|
||||
html:
|
||||
alt: Alternative text
|
||||
title: Title, text displayed when hovering
|
||||
- src: img/folder1/foo.png
|
||||
html:
|
||||
alt: Alternative text for the first picture in the image folder
|
||||
```
|
||||
|
||||
Optional parameters:
|
||||
This way, you can add any HTML attributes to the `<img>` element for the images you describe in the metadata file. In this example, you add this for two images, one of them is in a folder whose path you provided. You don't have to add information for all files.
|
||||
|
||||
- `caption-position` - determines the captions' position over the image. Options:
|
||||
- `bottom` (default)
|
||||
- `center`
|
||||
- `none` hides captions on the page (they will only show in PhotoSwipe)
|
||||
- `caption-effect` - determines if/how captions appear upon hover. Options:
|
||||
- `slide` (default)
|
||||
- `fade`
|
||||
- `none` (captions always visible)
|
||||
- `hover-transition` - determines if/how images change upon hover. Options:
|
||||
- not set - smooth transition (default)
|
||||
- `none` - hard transition
|
||||
This flexible way allows you to also translate metadata. Just use different `metadata` values to the shortcodes depending on the language.
|
||||
|
||||
Note that a `title` is also taken as a caption to the picture in order to reduce duplicated work.
|
||||
|
||||
## CSS Hackers
|
||||
## Credits
|
||||
|
||||
`snap-gallery.css` is designed to provide square tiles in a container with `max-width: 768px`.
|
||||
|
||||
Here are some pointers if you want to adapt the CSS:
|
||||
|
||||
- Change `.gallery {max-width: 768px;}` if you want a gallery wider than 768px.
|
||||
- Change `min-width` in the `@media` styles to change the screen widths at which the layout changes
|
||||
- Change `min-width: 9999px` in the last `@media` style to something sensible if you want to use a 4-tile layout
|
||||
- If you want more than 4 tiles per row, set `width` = 100% / number of tiles per row
|
||||
- `padding-bottom` = `width` gives square tiles. Change padding-bottom if you want some other aspect ratio, e.g. `width: 33.3%; padding-bottom: 25%` gives a 4:3 aspect ratio.
|
||||
The original inspiration for this shortcode came from [Li-Wen Yip's easy-gallery](https://github.com/liwenyip/hugo-easy-gallery). The first major version of this was already a 90% rewrite, and the current one has even less to do with it. However, the rewrite took some inspirations from [W3Schools](https://www.w3schools.com/howto/howto_js_lightbox.asp), thanks!
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
This repository follows the REUSE best practices for clear copyright and licensing information. The license texts for all used licenses can be found in the LICENSES/ directory under the root of this repository. Visit [reuse.software](https://reuse.software) for more information.
|
||||
|
||||
The main license of this repo is MIT.
|
||||
|
||||
260
assets/scss/snap-gallery.scss
Normal file
260
assets/scss/snap-gallery.scss
Normal file
@@ -0,0 +1,260 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2024 Max Mehl <https://mehl.mx>
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
$gap: var(--gap);
|
||||
|
||||
.snap-gallery,
|
||||
.snap-slideshow {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
margin-bottom: 15px;
|
||||
|
||||
/* Create equal columns in flexbox */
|
||||
.snap-image {
|
||||
cursor: var(--cursor);
|
||||
|
||||
/* Column amount and width are configurable by style variables */
|
||||
width: calc(100% / var(--columns) - #{$gap});
|
||||
min-width: var(--min-width);
|
||||
|
||||
img {
|
||||
aspect-ratio: var(--aspectratio);
|
||||
width: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.snap-gallery {
|
||||
flex-wrap: wrap;
|
||||
gap: $gap;
|
||||
}
|
||||
|
||||
.snap-slideshow {
|
||||
width: var(--slideshow-width);
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
|
||||
// Hide all contained images except the first
|
||||
.snap-image:not(:first-child) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
// Animation
|
||||
.snap-slideshow .snap-image,
|
||||
.snap-lightbox .snap-lightbox-inner {
|
||||
animation: 1s fade;
|
||||
|
||||
@keyframes fade {
|
||||
from {
|
||||
opacity: .4
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.snap-lightbox {
|
||||
/** Default lightbox to hidden */
|
||||
display: none;
|
||||
|
||||
/** Position and style */
|
||||
position: fixed;
|
||||
z-index: 999;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
|
||||
/* keep lightbox in middle. TODO: hacky, and not realibly in middle */
|
||||
&:before {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: 0;
|
||||
/* adjust for white space between pseudo element and next sibling */
|
||||
margin-right: -.25em;
|
||||
/* stretch line height */
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
// Disable cursor selection in lightbox and on slideshow, especially controls
|
||||
.snap-lightbox,
|
||||
.snap-slideshow {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
// /* Click on the complete background closes the lightbox */
|
||||
// /* Exception: arrow in gallery, they have higher z-index */
|
||||
// a.snap-lightbox-close {
|
||||
// position: fixed;
|
||||
// z-index: 800;
|
||||
// width: 100%;
|
||||
// height: 100%;
|
||||
// text-align: center;
|
||||
// white-space: nowrap;
|
||||
// top: 0;
|
||||
// left: 0;
|
||||
// }
|
||||
|
||||
/* Container for image */
|
||||
.snap-lightbox-inner {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
white-space: normal;
|
||||
max-width: 90%;
|
||||
max-height: 80%;
|
||||
height: 100%;
|
||||
|
||||
img {
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 767px) {
|
||||
max-width: 80%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Number text (1/3 etc) and captions*/
|
||||
.snap-numbertext {
|
||||
position: absolute;
|
||||
color: #f2f2f2;
|
||||
background-color: #000;
|
||||
font-size: 12px;
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
.snap-caption {
|
||||
bottom: 0;
|
||||
color: #f2f2f2;
|
||||
padding: 8px 0;
|
||||
left: 10%;
|
||||
width: 80%;
|
||||
text-shadow: 1px 1px 10px #000;
|
||||
font-weight: 700;
|
||||
font-size: 1.1em;
|
||||
text-align: center;
|
||||
|
||||
// As slideshow, position on picture on mid-screens and wider
|
||||
.snap-slideshow & {
|
||||
position: absolute;
|
||||
|
||||
@media screen and (max-width: 767px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
// In lightbox, always show below picture
|
||||
.snap-lightbox & {
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
// /* Caption text */
|
||||
// .caption-container {
|
||||
// text-align: center;
|
||||
// background-color: black;
|
||||
// padding: 2px 16px;
|
||||
// color: white;
|
||||
// }
|
||||
|
||||
/* prev/next arrows & close button */
|
||||
.snap-prev,
|
||||
.snap-next {
|
||||
color: #fff;
|
||||
text-decoration: none !important;
|
||||
font-size: 30px;
|
||||
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
z-index: 800;
|
||||
width: 5%;
|
||||
cursor: pointer;
|
||||
|
||||
// Slideshow specifics
|
||||
.snap-slideshow & {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
|
||||
height: 15%;
|
||||
width: 10%;
|
||||
min-height: 50px;
|
||||
min-width: 50px;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
transition: all 0.25s ease;
|
||||
}
|
||||
}
|
||||
|
||||
// Lightbox-specifics
|
||||
.snap-lightbox & {
|
||||
@media screen and (max-width: 767px) {
|
||||
width: 10%;
|
||||
}
|
||||
}
|
||||
|
||||
// Item containing arrow
|
||||
span {
|
||||
padding: 1%;
|
||||
|
||||
// Lightbox specifics
|
||||
.snap-lightbox & {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Position arrows for left/right
|
||||
.snap-prev {
|
||||
left: 0;
|
||||
|
||||
.snap-lightbox & span {
|
||||
left: 0.5%;
|
||||
}
|
||||
}
|
||||
|
||||
.snap-next {
|
||||
right: 0;
|
||||
|
||||
.snap-lightbox & span {
|
||||
right: 0.5%;
|
||||
}
|
||||
}
|
||||
|
||||
/* The Close Button */
|
||||
.snap-close {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: 15px;
|
||||
height: 50px;
|
||||
width: 50px;
|
||||
color: white;
|
||||
font-size: 50px;
|
||||
font-weight: bold;
|
||||
z-index: 900;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: #999;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
18
layouts/partials/gallery.html
Normal file
18
layouts/partials/gallery.html
Normal file
@@ -0,0 +1,18 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2024 Max Mehl <https://mehl.mx>
|
||||
SPDX-License-Identifier: MIT
|
||||
-->
|
||||
{{- $imgs := .imgs -}}
|
||||
{{- $galno := .galno -}}
|
||||
<div class="snap-gallery" style="--columns:{{ default 4 .columns }};--min-width:{{ default "200px" .minwidth }};--gap:10px;--cursor:{{ .cursor }};--aspectratio:{{ safeCSS (default "16/10" .aspectratio) }};">
|
||||
{{- range $i, $img := $imgs }}
|
||||
<div class="snap-image">
|
||||
<img
|
||||
src="{{ relURL $img.src }}"
|
||||
{{ range $attr, $value := $img.html -}}
|
||||
{{ safeHTMLAttr $attr }}={{ $value }}
|
||||
{{ end -}}
|
||||
onclick="openLightbox({{ $galno }});openLightboxItem({{ $galno }}, {{ add $i 1 }});" />
|
||||
</div>
|
||||
{{- end }}
|
||||
</div>
|
||||
28
layouts/partials/lightbox.html
Normal file
28
layouts/partials/lightbox.html
Normal file
@@ -0,0 +1,28 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2024 Max Mehl <https://mehl.mx>
|
||||
SPDX-License-Identifier: MIT
|
||||
-->
|
||||
{{- $imgs := .imgs -}}
|
||||
{{- $galno := .galno -}}
|
||||
<div class="snap-lightbox" id="snap-lightbox-{{ $galno }}">
|
||||
{{- range $i, $img := $imgs }}
|
||||
<div class="snap-lightbox-inner">
|
||||
<div class="snap-numbertext">{{ add $i 1 }} / {{ len $imgs }}</div>
|
||||
<img
|
||||
src="{{ relURL $img.src }}"
|
||||
{{ range $attr, $value := $img.html -}}
|
||||
{{ safeHTMLAttr $attr }}={{ $value }}
|
||||
{{ end -}}
|
||||
/>
|
||||
{{- with $img.html.title }}
|
||||
<div class="snap-caption">{{ . }}</div>
|
||||
{{- end }}
|
||||
</div>
|
||||
{{- end -}}
|
||||
|
||||
<!-- Close and Next/previous controls -->
|
||||
<span class="snap-close" onclick="closeLightbox({{ $galno }})">×</span>
|
||||
|
||||
<a class="snap-prev" onclick="moveLightboxItem({{ $galno }}, -1)"><span>❮</span></a>
|
||||
<a class="snap-next" onclick="moveLightboxItem({{ $galno }}, 1)"><span>❯</span></a>
|
||||
</div>
|
||||
25
layouts/partials/slideshow.html
Normal file
25
layouts/partials/slideshow.html
Normal file
@@ -0,0 +1,25 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2024 Max Mehl <https://mehl.mx>
|
||||
SPDX-License-Identifier: MIT
|
||||
-->
|
||||
{{- $imgs := .imgs -}}
|
||||
{{- $galno := .galno -}}
|
||||
<div class="snap-slideshow" id="snap-slideshow-{{ $galno }}" style="--columns:1;--min-width:0px;--gap:0px;--slideshow-width:{{ default "100%" .width }};--cursor:{{ .cursor }};--aspectratio:{{ safeCSS (default "16/10" .aspectratio) }};">
|
||||
{{- range $i, $img := $imgs }}
|
||||
<div class="snap-image">
|
||||
<div class="snap-numbertext">{{ add $i 1 }} / {{ len $imgs }}</div>
|
||||
{{- with $img.html.title }}
|
||||
<div class="snap-caption">{{ . }}</div>
|
||||
{{- end }}
|
||||
<img
|
||||
src="{{ relURL $img.src }}"
|
||||
{{ range $attr, $value := $img.html -}}
|
||||
{{ safeHTMLAttr $attr }}={{ $value }}
|
||||
{{ end -}}
|
||||
onclick="openLightbox({{ $galno }});openLightboxItem({{ $galno }}, {{ add $i 1 }});" />
|
||||
</div>
|
||||
{{- end }}
|
||||
|
||||
<a class="snap-prev" onclick="moveSlideshowItem({{ $galno }}, -1)"><span>❮</span></a>
|
||||
<a class="snap-next" onclick="moveSlideshowItem({{ $galno }}, 1)"><span>❯</span></a>
|
||||
</div>
|
||||
@@ -1,33 +0,0 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2019 Li-Wen Yip <liwen.yip@gmail.com>
|
||||
SPDX-FileCopyrightText: 2019 Max Mehl <mail@mehl.mx>
|
||||
SPDX-License-Identifier: MIT
|
||||
|
||||
Put this file in /layouts/shortcodes/figure.html
|
||||
NB this overrides Hugo's built-in "figure" shortcode but is backwards compatible
|
||||
Documentation and licence at https://github.com/liwenyip/hugo-easy-gallery/
|
||||
-->
|
||||
<!-- count how many times we've called this shortcode; load the css if it's the first time -->
|
||||
{{- if not ($.Page.Scratch.Get "figurecount") }}<link rel="stylesheet" href="/css/snap-gallery.css" />{{ end }}
|
||||
{{- $.Page.Scratch.Add "figurecount" 1 -}}
|
||||
<!-- use either src or link-thumb for thumbnail image -->
|
||||
{{- $thumb := .Get "src" | default (printf "%s." (.Get "thumb") | replace (.Get "link") ".") }}
|
||||
<div class="box{{ with .Get "caption-position" }} fancy-figure caption-position-{{.}}{{end}}{{ with .Get "caption-effect" }} caption-effect-{{.}}{{end}}" {{ with .Get "width" }}style="max-width:{{.}}"{{end}}>
|
||||
<figure {{ with .Get "class" }}class="{{.}}"{{ end }} itemprop="associatedMedia" itemscope itemtype="http://schema.org/ImageObject">
|
||||
<div class="img"{{ if .Parent }} style="background-image: url('{{ $thumb | relURL }}');"{{ end }}{{ with .Get "size" }} data-size="{{.}}"{{ end }}>
|
||||
<img itemprop="thumbnail" src="{{ $thumb | relURL }}" {{ with .Get "alt" | default (.Get "caption") }}alt="{{.}}"{{ end }}/><!-- <img> hidden if in .gallery -->
|
||||
</div>
|
||||
{{ with .Get "link" | default (.Get "src") }}<a href="{{.}}" itemprop="contentUrl"></a>{{ end }}
|
||||
{{- if or (or (.Get "title") (.Get "caption")) (.Get "attr")}}
|
||||
<figcaption>
|
||||
{{- with .Get "title" }}<h4>{{.}}</h4>{{ end }}
|
||||
{{- if or (.Get "caption") (.Get "attr")}}
|
||||
<p>
|
||||
{{- .Get "caption" -}}
|
||||
{{- with .Get "attrlink"}}<a href="{{.}}">{{ .Get "attr" }}</a>{{ else }}{{ .Get "attr"}}{{ end -}}
|
||||
</p>
|
||||
{{- end }}
|
||||
</figcaption>
|
||||
{{- end }}
|
||||
</figure>
|
||||
</div>
|
||||
@@ -1,45 +0,0 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2019 Li-Wen Yip <liwen.yip@gmail.com>
|
||||
SPDX-FileCopyrightText: 2019 Max Mehl <mail@mehl.mx>
|
||||
SPDX-License-Identifier: MIT
|
||||
|
||||
Put this file in /layouts/shortcodes/gallery.html
|
||||
Documentation and licence at https://github.com/liwenyip/hugo-easy-gallery/
|
||||
-->
|
||||
<!-- count how many times we've called this shortcode; load the css if it's the first time -->
|
||||
<link rel="stylesheet" href="/css/snap-gallery.css" />
|
||||
{{- $.Page.Scratch.Add "figurecount" 1 }}
|
||||
{{ $baseURL := .Site.BaseURL }}
|
||||
<div class="gallery caption-position-{{ with .Get "caption-position" | default "bottom" }}{{.}}{{end}} caption-effect-{{ with .Get "caption-effect" | default "slide" }}{{.}}{{end}} hover-effect-{{ with .Get "hover-effect" | default "zoom" }}{{.}}{{end}} {{ if ne (.Get "hover-transition") "none" }}hover-transition{{end}}" itemscope itemtype="http://schema.org/ImageGallery">
|
||||
{{- with (.Get "dir") -}}
|
||||
<!-- If a directory was specified, generate figures for all of the images in the directory -->
|
||||
{{- $files := readDir (print "/static/" .) }}
|
||||
{{- range $files -}}
|
||||
<!-- skip files that aren't images, or that inlcude the thumb suffix in their name -->
|
||||
{{- $thumbext := $.Get "thumb" | default "-thumb" }}
|
||||
{{- $isthumb := .Name | findRE ($thumbext | printf "%s\\.") }}<!-- is the current file a thumbnail image? -->
|
||||
{{- $isimg := lower .Name | findRE "\\.(gif|jpg|jpeg|tiff|png|bmp)" }}<!-- is the current file an image? -->
|
||||
{{- if and $isimg (not $isthumb) }}
|
||||
{{- $caption := .Name | replaceRE "\\..*" "" | humanize }}<!-- humanized filename without extension -->
|
||||
{{- $linkURL := print $baseURL ($.Get "dir") "" .Name | absURL }}<!-- absolute URL to hi-res image -->
|
||||
{{- $thumb := .Name | replaceRE "(\\.)" ($thumbext | printf "%s.") }}<!-- filename of thumbnail image -->
|
||||
{{- $thumbexists := where $files "Name" $thumb }}<!-- does a thumbnail image exist? -->
|
||||
{{- $thumbURL := print $baseURL ($.Get "dir") "/" $thumb | absURL }}<!-- absolute URL to thumbnail image -->
|
||||
<div class="box">
|
||||
<figure itemprop="associatedMedia" itemscope itemtype="http://schema.org/ImageObject">
|
||||
<div class="img" style="background-image: url('{{ if $thumbexists }}{{ $thumbURL }}{{ else }}{{ $linkURL }}{{ end }}');" >
|
||||
<img itemprop="thumbnail" src="{{ if $thumbexists }}{{ $thumbURL }}{{ else }}{{ $linkURL }}{{ end }}" alt="{{ $caption }}" /><!-- <img> hidden if in .gallery -->
|
||||
</div>
|
||||
<figcaption>
|
||||
<p>{{ $caption }}</p>
|
||||
</figcaption>
|
||||
<a href="{{ $linkURL }}" itemprop="contentUrl" class="hiddenlink" target="_blank"></a><!-- put <a> last so it is stacked on top -->
|
||||
</figure>
|
||||
</div>
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- else -}}
|
||||
<!-- If no directory was specified, include any figure shortcodes called within the gallery -->
|
||||
{{ .Inner }}
|
||||
{{- end }}
|
||||
</div>
|
||||
87
layouts/shortcodes/snap-gallery.html
Normal file
87
layouts/shortcodes/snap-gallery.html
Normal file
@@ -0,0 +1,87 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2024 Max Mehl <https://mehl.mx>
|
||||
SPDX-License-Identifier: MIT
|
||||
-->
|
||||
{{/* Count number of gallery on page */}}
|
||||
{{- $.Page.Scratch.Add "gallerycount" 1 -}}
|
||||
{{- $galno := $.Page.Scratch.Get "gallerycount" -}}
|
||||
|
||||
{{/* Load CSS/JS and make sure it is only loaded once */}}
|
||||
{{- if eq ($.Page.Scratch.Get "gallerycount") 1 -}}
|
||||
{{- with resources.Get "scss/snap-gallery.scss" | toCSS -}}
|
||||
<link rel="stylesheet" href="{{ .RelPermalink }}" crossorigin="anonymous">
|
||||
{{- end -}}
|
||||
<script src="/js/snap-gallery.js"></script>
|
||||
{{- end -}}
|
||||
|
||||
{{/* Initialise index of this gallery */}}
|
||||
<script>imageIndex[{{ $galno }}] = 1;</script>
|
||||
|
||||
{{/* Initialise variables holding image paths and extension */}}
|
||||
{{- $imgs_collect := slice -}}
|
||||
{{- $imgs := slice -}}
|
||||
{{- $img_exts := slice ".jpg" ".jpeg" ".png" ".gif" ".webp" ".tiff" -}}
|
||||
|
||||
{{/* Get information from optional metadata file */}}
|
||||
{{- $metadata := dict -}}
|
||||
{{- with .Params.metadata -}}
|
||||
{{- $metadata = index $.Site.Data . -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/* Get images from src Param, separated by comma */}}
|
||||
{{- range (split .Params.src ",") -}}
|
||||
{{- $img := (trim . " ") -}}
|
||||
{{- $img_static := print "/static/" $img -}}
|
||||
{{/* Only proceed when path exists */}}
|
||||
{{- if os.FileExists $img_static -}}
|
||||
{{/* If current item is a directory, range each of them, and add with full path to the slice */}}
|
||||
{{- if (os.Stat $img_static).IsDir -}}
|
||||
{{- range readDir $img_static -}}
|
||||
{{- $imgs_collect = $imgs_collect | append (path.Join $img .Name ) -}}
|
||||
{{- end -}}
|
||||
{{/* If a single file, just add it to the slice */}}
|
||||
{{- else -}}
|
||||
{{- $imgs_collect = $imgs_collect | append $img -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/* Filter collected files, store them as slice of dicts/maps */}}
|
||||
{{- range $imgs_collect -}}
|
||||
{{/* Only process files if they are recognised as an image */}}
|
||||
{{- if in $img_exts (lower (path.Ext .)) -}}
|
||||
{{/* Create a dict holding path and optional metadata */}}
|
||||
{{- $imgdict := dict "src" . -}}
|
||||
{{/* If metadata for this image path found, add all of it to dict */}}
|
||||
{{- $img_metadata := where $metadata "src" . -}}
|
||||
{{- with $img_metadata -}}
|
||||
{{- $imgdict = merge $imgdict (index . 0) -}}
|
||||
{{- end -}}
|
||||
{{/* Add final dict to slice */}}
|
||||
{{- $imgs = $imgs | append $imgdict -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/* Define cursor when hovering over images, depending on lightbox status */}}
|
||||
{{- $lightbox := (default true .Params.lightbox) -}}
|
||||
{{- $cursor := "auto" -}}
|
||||
{{- if $lightbox }}{{ $cursor = "zoom-in" }}{{ end -}}
|
||||
|
||||
{{/* Visible images in separate modes */}}
|
||||
{{- $mode := default "gallery" .Params.mode -}}
|
||||
{{/* Gallery mode */}}
|
||||
{{- if eq $mode "gallery" -}}
|
||||
{{- partial "gallery" (dict "columns" .Params.columns "minwidth" .Params.minwidth "imgs" $imgs "galno" $galno "cursor" $cursor "aspectratio" .Params.aspectratio) -}}
|
||||
{{/* Slideshow mode */}}
|
||||
{{- else if eq $mode "slideshow" -}}
|
||||
{{- partial "slideshow" (dict "imgs" $imgs "galno" $galno "width" .Params.slideshowwidth "cursor" $cursor "aspectratio" .Params.aspectratio) -}}
|
||||
{{/* Set autorotate timer for slideshow, if configured (default: yes) */}}
|
||||
{{- if (default true .Params.slideshowrotate) -}}
|
||||
<script>const autoSlideshow = setInterval(moveSlideshowItem, {{ default 5000 .Params.slideshowrotate_timer }}, {{ $galno }}, 1, "auto");</script>
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/* The Modal/Lightbox */}}
|
||||
{{ if $lightbox -}}
|
||||
{{- partial "lightbox" (dict "galno" $galno "imgs" $imgs) -}}
|
||||
{{- end -}}
|
||||
@@ -1,165 +0,0 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2019 Li-Wen Yip <liwen.yip@gmail.com>
|
||||
SPDX-FileCopyrightText: 2019 Max Mehl <mail@mehl.mx>
|
||||
SPDX-License-Identifier: MIT
|
||||
|
||||
Put this file in /static/css/hugo-easy-gallery.css
|
||||
Documentation and licence at https://github.com/liwenyip/hugo-easy-gallery/
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
Grid Layout Styles
|
||||
*/
|
||||
.gallery {
|
||||
overflow: hidden;
|
||||
margin: 10px;
|
||||
max-width: 768px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.gallery .box {
|
||||
float: left;
|
||||
position: relative;
|
||||
/* Default: 1 tile wide */
|
||||
width: 100%;
|
||||
padding-bottom: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
@media only screen and (min-width : 365px) {
|
||||
/* Tablet view: 2 tiles */
|
||||
.gallery .box {
|
||||
width: 50%;
|
||||
padding-bottom: 50%;
|
||||
}
|
||||
}
|
||||
@media only screen and (min-width : 480px) {
|
||||
/* Small desktop / ipad view: 3 tiles */
|
||||
.gallery .box {
|
||||
width: 33.3%;
|
||||
padding-bottom: 33.3%; /* */
|
||||
}
|
||||
}
|
||||
@media only screen and (min-width : 9999px) {
|
||||
/* Medium desktop: 4 tiles */
|
||||
.box {
|
||||
width: 25%;
|
||||
padding-bottom: 25%;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Transition styles
|
||||
*/
|
||||
.gallery.hover-transition figure,
|
||||
.gallery.hover-effect-zoom .img,
|
||||
.gallery:not(.caption-effect-appear) figcaption,
|
||||
.fancy-figure:not(.caption-effect-appear) figcaption {
|
||||
-webkit-transition: all 0.3s ease-in-out;
|
||||
-moz-transition: all 0.3s ease-in-out;
|
||||
-o-transition: all 0.3s ease-in-out;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
/*
|
||||
figure styles
|
||||
*/
|
||||
figure {
|
||||
position:relative; /* purely to allow absolution positioning of figcaption */
|
||||
overflow: hidden;
|
||||
}
|
||||
.gallery figure {
|
||||
position: absolute;
|
||||
left: 5px;
|
||||
right: 5px;
|
||||
top: 5px;
|
||||
bottom: 5px;
|
||||
}
|
||||
.gallery.hover-effect-grow figure:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
.gallery.hover-effect-shrink figure:hover {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
.gallery.hover-effect-slidedown figure:hover {
|
||||
transform: translateY(5px);
|
||||
}
|
||||
.gallery.hover-effect-slideup figure:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
/*
|
||||
img / a styles
|
||||
*/
|
||||
|
||||
.gallery .img {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background-size: cover;
|
||||
background-position: 50% 50%;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
.gallery.hover-effect-zoom figure:hover .img {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
.gallery img {
|
||||
display: none; /* only show the img if not inside a gallery */
|
||||
}
|
||||
figure a {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
figcaption styles
|
||||
*/
|
||||
.gallery figcaption,
|
||||
.fancy-figure figcaption {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: #000;
|
||||
color: #FFF;
|
||||
text-align: center;
|
||||
font-size: 75%; /* change this if you want bigger text */
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
opacity: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
.gallery.caption-position-none figcaption,
|
||||
.fancy-figure.caption-position-none figcaption {
|
||||
display: none;
|
||||
}
|
||||
.gallery.caption-position-center figcaption,
|
||||
.fancy-figure.caption-position-center figcaption {
|
||||
top: 0;
|
||||
padding: 40% 5px;
|
||||
}
|
||||
.gallery.caption-position-bottom figcaption,
|
||||
.fancy-figure.caption-position-bottom figcaption {
|
||||
padding: 5px;
|
||||
}
|
||||
.gallery.caption-effect-fade figure:not(:hover) figcaption,
|
||||
.gallery.caption-effect-appear figure:not(:hover) figcaption,
|
||||
.fancy-figure.caption-effect-fade figure:not(:hover) figcaption,
|
||||
.fancy-figure.caption-effect-appear figure:not(:hover) figcaption {
|
||||
background: rgba(0, 0, 0, 0);
|
||||
opacity: 0;
|
||||
}
|
||||
.gallery.caption-effect-slide.caption-position-bottom figure:not(:hover) figcaption,
|
||||
.fancy-figure.caption-effect-slide.caption-position-bottom figure:not(:hover) figcaption {
|
||||
margin-bottom: -100%;
|
||||
}
|
||||
.gallery.caption-effect-slide.caption-position-center figure:not(:hover) figcaption,
|
||||
.fancy-figure.caption-effect-slide.caption-position-center figure:not(:hover) figcaption {
|
||||
top: 100%;
|
||||
}
|
||||
figcaption p {
|
||||
margin: auto; /* override style in theme */
|
||||
}
|
||||
80
static/js/snap-gallery.js
Normal file
80
static/js/snap-gallery.js
Normal file
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2023 Max Mehl <https://mehl.mx>
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
// Variables
|
||||
var lightbox_baseid = "snap-lightbox-";
|
||||
var slideshow_baseid = "snap-slideshow-";
|
||||
//var imageIndex = {1: 1, 2: 1, 3: 1};
|
||||
var imageIndex = {}
|
||||
|
||||
// Open the Lightbox
|
||||
function openLightbox(id) {
|
||||
document.getElementById(lightbox_baseid + id).style.display = "block";
|
||||
|
||||
// Kill automatic slideshow when lightbox opened
|
||||
try {
|
||||
clearInterval(autoSlideshow);
|
||||
} catch (e) {
|
||||
console.log("Lightbox error: " + e)
|
||||
}
|
||||
}
|
||||
|
||||
// Close the Lightbox
|
||||
function closeLightbox(id) {
|
||||
document.getElementById(lightbox_baseid + id).style.display = "none";
|
||||
}
|
||||
|
||||
// Move lightbox to the specified item
|
||||
function openLightboxItem(id, n) {
|
||||
showItem(lightbox_baseid, id, imageIndex[id] = n, ".snap-lightbox-inner");
|
||||
}
|
||||
|
||||
// Next/previous controls for lightbox
|
||||
function moveLightboxItem(id, n) {
|
||||
showItem(lightbox_baseid, id, imageIndex[id] += n, ".snap-lightbox-inner");
|
||||
}
|
||||
|
||||
// Next/previous controls for slideshow
|
||||
function moveSlideshowItem(id, n, mode) {
|
||||
showItem(slideshow_baseid, id, imageIndex[id] += n, ".snap-image");
|
||||
|
||||
// Kill automatic slideshow once the slideshow has been moved manually
|
||||
if (mode !== "auto") {
|
||||
clearInterval(autoSlideshow);
|
||||
}
|
||||
}
|
||||
|
||||
// In the slideshow or lightbox, make a specific image visible, make others hidden
|
||||
function showItem(baseId, id, n, className) {
|
||||
// Get elements that shall be rotated
|
||||
const element = document.getElementById(baseId + id);
|
||||
const items = element.querySelectorAll(className);
|
||||
|
||||
// Increment item index
|
||||
const updateIndex = () => {
|
||||
if (n > items.length) {
|
||||
imageIndex[id] = 1;
|
||||
}
|
||||
if (n < 1) {
|
||||
imageIndex[id] = items.length;
|
||||
}
|
||||
};
|
||||
|
||||
// hide all selected elements
|
||||
const hideAllItems = () => {
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
items[i].style.display = "none";
|
||||
}
|
||||
};
|
||||
|
||||
// make desired item visible
|
||||
const showCurrentItem = () => {
|
||||
items[imageIndex[id] - 1].style.display = "inline-block";
|
||||
};
|
||||
|
||||
updateIndex();
|
||||
hideAllItems();
|
||||
showCurrentItem();
|
||||
}
|
||||
@@ -1,17 +1,16 @@
|
||||
# SPDX-FileCopyrightText: 2019 Li-Wen Yip <liwen.yip@gmail.com>
|
||||
# SPDX-FileCopyrightText: 2019 Max Mehl <mail@mehl.mx>
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
name: Snap Gallery
|
||||
license: MIT
|
||||
licenselink: https://src.mehl.mx/mxmehl/hugo-snap-gallery/src/branch/master/LICENSES/MIT.txt
|
||||
description: Automagical css image gallery in Hugo using shortcodes. Fork of https://github.com/liwenyip/hugo-easy-gallery
|
||||
description: Advanced image gallery using shortcodes. It offers a lightbox and is also usable for single images. CSS only, no Javascript!
|
||||
homepage: https://src.mehl.mx/mxmehl/hugo-snap-gallery
|
||||
tags:
|
||||
- component
|
||||
features:
|
||||
- gallery
|
||||
min_version: 0.50.0
|
||||
min_version: 0.60.0
|
||||
|
||||
author:
|
||||
name: Max Mehl
|
||||
|
||||
Reference in New Issue
Block a user