jekyll-before-after 0.1.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +26 -0
- data/README.md +32 -63
- data/jekyll-before-after.gemspec +1 -1
- data/lib/jekyll/before_after_generator.rb +36 -0
- data/lib/jekyll/before_after_tag.rb +6 -161
- data/lib/jekyll-before-after.rb +25 -0
- metadata +3 -3
- data/_before-after.scss +0 -144
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 95018375cf799be55f8ea6697b6e75001a0273bf053d6cfd70f584a6c0e31a9e
|
|
4
|
+
data.tar.gz: 517c6093fc0789f13d357322d2ec9eeacba39df886e55fc5f054474bfe7fe182
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ebc5888a4e142f4247ba391cc4528b3a718830bc12fd2a0358a00d30ce307a9b72e723e5600cd0d45a1d9b103ee8e68a8a8277bf87e1db72d1324397596fe47e
|
|
7
|
+
data.tar.gz: ae8c0aaf89ee1c78345cfd4c80039dacfa4e32be35a6a6449c7431804726225cca8cc112f8e440a71e043862ae356f65696de0c79a67958b1cbb00310c22a90f
|
data/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,32 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
This project adheres to [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|
5
5
|
|
|
6
|
+
## [1.0.0] - 2026-08-29
|
|
7
|
+
|
|
8
|
+
### Changed
|
|
9
|
+
- **MAJOR: Refactored to use [img-comparison-slider](https://github.com/sneas/img-comparison-slider) web component**
|
|
10
|
+
- Replaced custom JavaScript implementation with proven, battle-tested web component
|
|
11
|
+
- Dramatically simplified codebase (no custom JS initialization needed)
|
|
12
|
+
- Improved performance and reliability
|
|
13
|
+
- Better accessibility with native web component semantics
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
- `orientation="vertical"` attribute for vertical comparisons
|
|
17
|
+
- Automatic CDN injection of img-comparison-slider library (no manual setup required)
|
|
18
|
+
- Full CSS custom property theming support via img-comparison-slider
|
|
19
|
+
|
|
20
|
+
### Removed
|
|
21
|
+
- Custom JavaScript slider implementation
|
|
22
|
+
- `_before-after.scss` (styling now via CSS custom properties)
|
|
23
|
+
- Configurable start position, overlay labels, srcset attributes (use CSS for customization)
|
|
24
|
+
- ResizeObserver logic (handled by web component)
|
|
25
|
+
|
|
26
|
+
### Migration Guide
|
|
27
|
+
If upgrading from 0.1.0:
|
|
28
|
+
- All existing `{% before_after %}` tags work unchanged
|
|
29
|
+
- Custom styling should migrate to CSS custom properties (see README)
|
|
30
|
+
- No configuration changes needed — it just works
|
|
31
|
+
|
|
6
32
|
## [0.1.0] - 2026-08-22
|
|
7
33
|
|
|
8
34
|
### Added
|
data/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# jekyll-before-after
|
|
2
2
|
|
|
3
|
-
A Jekyll plugin providing a `{% before_after %}` Liquid tag for rendering a
|
|
3
|
+
A Jekyll plugin providing a `{% before_after %}` Liquid tag for rendering a drag-to-reveal image comparison slider. Built on the proven [img-comparison-slider](https://github.com/sneas/img-comparison-slider) web component. Supports horizontal and vertical orientations, responsive images, keyboard navigation, touch support, and full CSS custom property theming. Pure vanilla JS with zero external dependencies — GitHub Pages compatible.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -17,18 +17,14 @@ plugins:
|
|
|
17
17
|
- jekyll-before-after
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
Copy `_before-after.scss` to your site's CSS directory and import it.
|
|
21
|
-
|
|
22
20
|
Run `bundle install`.
|
|
23
21
|
|
|
24
22
|
## Usage
|
|
25
23
|
|
|
26
24
|
```liquid
|
|
27
25
|
{% before_after
|
|
28
|
-
before="/images/
|
|
29
|
-
after="/images/
|
|
30
|
-
before_label="2018"
|
|
31
|
-
after_label="2024"
|
|
26
|
+
before="/images/before.jpg"
|
|
27
|
+
after="/images/after.jpg"
|
|
32
28
|
%}
|
|
33
29
|
```
|
|
34
30
|
|
|
@@ -38,96 +34,69 @@ Run `bundle install`.
|
|
|
38
34
|
|--------|---------|-------------|
|
|
39
35
|
| `before` | _(required)_ | Path to the "before" image |
|
|
40
36
|
| `after` | _(required)_ | Path to the "after" image |
|
|
41
|
-
| `before_label` | `"Before"` | Label overlaid on the before side |
|
|
42
|
-
| `after_label` | `"After"` | Label overlaid on the after side |
|
|
43
|
-
| `start` | `50` | Initial slider position (0–100) |
|
|
44
37
|
| `orientation` | `horizontal` | `horizontal` or `vertical` |
|
|
45
|
-
| `alt_before` | `""` | Alt text for the before image |
|
|
46
|
-
| `alt_after` | `""` | Alt text for the after image |
|
|
47
|
-
| `srcset_before` | `""` | `srcset` value for responsive before image |
|
|
48
|
-
| `srcset_after` | `""` | `srcset` value for responsive after image |
|
|
49
|
-
| `sizes` | `"(max-width: 768px) 100vw, 100vw"` | `sizes` attribute when using srcset |
|
|
50
38
|
|
|
51
|
-
###
|
|
39
|
+
### Examples
|
|
52
40
|
|
|
53
|
-
|
|
41
|
+
#### Horizontal comparison
|
|
54
42
|
|
|
55
43
|
```liquid
|
|
56
44
|
{% before_after
|
|
57
|
-
before="/images/street-
|
|
58
|
-
after="/images/street-
|
|
59
|
-
srcset_before="/images/street-before-480.jpg 480w, /images/street-before-960.jpg 960w"
|
|
60
|
-
srcset_after="/images/street-after-480.jpg 480w, /images/street-after-960.jpg 960w"
|
|
61
|
-
sizes="(max-width: 600px) 100vw, 80vw"
|
|
45
|
+
before="/images/main-street-2018.jpg"
|
|
46
|
+
after="/images/main-street-2024.jpg"
|
|
62
47
|
%}
|
|
63
48
|
```
|
|
64
49
|
|
|
65
|
-
|
|
50
|
+
#### Vertical comparison
|
|
66
51
|
|
|
67
52
|
```liquid
|
|
68
53
|
{% before_after
|
|
69
54
|
before="/images/building-2015.jpg"
|
|
70
55
|
after="/images/building-2025.jpg"
|
|
71
|
-
orientation=vertical
|
|
72
|
-
before_label="2015"
|
|
73
|
-
after_label="2025"
|
|
56
|
+
orientation="vertical"
|
|
74
57
|
%}
|
|
75
58
|
```
|
|
76
59
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
Multiple sliders on the same page are fully supported. The JavaScript block is emitted only once per page regardless of how many sliders you include.
|
|
60
|
+
#### Multiple sliders
|
|
80
61
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
Set site-wide defaults in `_config.yml`:
|
|
84
|
-
|
|
85
|
-
```yaml
|
|
86
|
-
before_after:
|
|
87
|
-
start: 50
|
|
88
|
-
orientation: horizontal
|
|
89
|
-
before_label: "Before"
|
|
90
|
-
after_label: "After"
|
|
91
|
-
```
|
|
62
|
+
Multiple sliders on the same page are fully supported. The library script is injected only once per page.
|
|
92
63
|
|
|
93
64
|
## Styling
|
|
94
65
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
| `--ba-label-radius` | `3px` | Label border radius |
|
|
106
|
-
| `--ba-radius` | `4px` | Container border radius |
|
|
107
|
-
| `--ba-focus-ring` | `rgba(255,255,255,0.8)` | Keyboard focus ring color |
|
|
108
|
-
|
|
109
|
-
### Example override
|
|
110
|
-
|
|
111
|
-
```scss
|
|
112
|
-
.before-after {
|
|
113
|
-
--ba-handle-color: #e67e22;
|
|
114
|
-
--ba-knob-size: 3rem;
|
|
115
|
-
--ba-label-bg: rgba(0, 0, 0, 0.75);
|
|
66
|
+
All visual properties are controlled via CSS custom properties. Target the `img-comparison-slider` element:
|
|
67
|
+
|
|
68
|
+
```css
|
|
69
|
+
img-comparison-slider {
|
|
70
|
+
--divider-width: 4px;
|
|
71
|
+
--divider-color: #fff;
|
|
72
|
+
--handle-width: 60px;
|
|
73
|
+
--handle-height: 60px;
|
|
74
|
+
--handle-opacity: 0.3;
|
|
75
|
+
--handle-shadow: 0 2px 8px rgba(0, 0, 0, 0.35);
|
|
116
76
|
}
|
|
117
77
|
```
|
|
118
78
|
|
|
79
|
+
See [img-comparison-slider docs](https://img-comparison-slider.sneas.io/) for all available CSS custom properties.
|
|
80
|
+
|
|
119
81
|
## Interaction
|
|
120
82
|
|
|
121
|
-
- **Drag** the
|
|
83
|
+
- **Drag** the divider to reveal
|
|
122
84
|
- **Click** anywhere on the image to jump the slider to that position
|
|
123
|
-
- **Keyboard**: focus the
|
|
85
|
+
- **Keyboard**: focus the slider and use arrow keys
|
|
124
86
|
- **Touch** and stylus supported via Pointer Events API
|
|
125
87
|
|
|
88
|
+
## How it works
|
|
89
|
+
|
|
90
|
+
This plugin wraps the lightweight [img-comparison-slider](https://github.com/sneas/img-comparison-slider) web component, loading it from CDN. The Liquid tag converts your `{% before_after %}` markup into the web component format, and the plugin injects the library script into the page once per build.
|
|
91
|
+
|
|
126
92
|
## Requirements
|
|
127
93
|
|
|
128
94
|
- Jekyll >= 3.9
|
|
129
95
|
- Ruby >= 2.7
|
|
96
|
+
- Modern browser with Web Components support (all modern browsers)
|
|
130
97
|
|
|
131
98
|
## License
|
|
132
99
|
|
|
133
100
|
MIT — see [LICENSE.txt](LICENSE.txt).
|
|
101
|
+
|
|
102
|
+
The plugin wraps [img-comparison-slider](https://github.com/sneas/img-comparison-slider) by sneas, also licensed under MIT.
|
data/jekyll-before-after.gemspec
CHANGED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jekyll
|
|
4
|
+
class BeforeAfterGenerator < Generator
|
|
5
|
+
safe true
|
|
6
|
+
priority :highest
|
|
7
|
+
|
|
8
|
+
def generate(site)
|
|
9
|
+
# Create the CSS as a virtual file that can be included
|
|
10
|
+
css_content = %q{<style>
|
|
11
|
+
.before-after{position:relative;overflow:hidden;cursor:col-resize;user-select:none;touch-action:none;border-radius:var(--ba-radius, 4px);background:var(--ba-bg, #000);margin-bottom:2rem}
|
|
12
|
+
.before-after[data-orientation="vertical"]{cursor:row-resize}
|
|
13
|
+
.before-after--dragging{cursor:grabbing}
|
|
14
|
+
.before-after__after{position:relative;display:block}
|
|
15
|
+
.before-after__after img,.before-after__after picture img{display:block;width:100%;height:auto;pointer-events:none}
|
|
16
|
+
.before-after__before{position:absolute;inset:0;z-index:1;clip-path:inset(0 50% 0 0)}
|
|
17
|
+
.before-after__before img,.before-after__before picture img{display:block;width:100%;height:100%;object-fit:cover;pointer-events:none}
|
|
18
|
+
.before-after__label{position:absolute;bottom:.75rem;z-index:2;padding:.25rem .6rem;font-size:.75rem;font-weight:600;letter-spacing:.04em;text-transform:uppercase;background:var(--ba-label-bg, rgba(0, 0, 0, 0.55));color:var(--ba-label-color, #fff);border-radius:var(--ba-label-radius, 3px);pointer-events:none}
|
|
19
|
+
.before-after__label--before{left:.75rem}
|
|
20
|
+
.before-after__label--after{right:.75rem}
|
|
21
|
+
.before-after__handle{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);z-index:10;display:flex;align-items:center;justify-content:center;outline:0}
|
|
22
|
+
.before-after__handle:focus-visible .before-after__handle-knob{box-shadow:0 0 0 3px var(--ba-focus-ring, rgba(255, 255, 255, 0.8))}
|
|
23
|
+
.before-after__handle-line{position:absolute;background:var(--ba-handle-color, #fff);opacity:.85}
|
|
24
|
+
.before-after:not([data-orientation="vertical"]) .before-after__handle-line{width:var(--ba-line-width, 2px);top:-200%;bottom:-200%;left:50%;transform:translateX(-50%)}
|
|
25
|
+
[data-orientation="vertical"] .before-after__handle-line{height:var(--ba-line-width, 2px);left:-200%;right:-200%;top:50%;transform:translateY(-50%)}
|
|
26
|
+
.before-after__handle-knob{position:relative;z-index:1;width:var(--ba-knob-size, 2.75rem);height:var(--ba-knob-size, 2.75rem);border-radius:50%;background:var(--ba-handle-color, #fff);color:var(--ba-knob-icon-color, #333);display:flex;align-items:center;justify-content:center;box-shadow:0 2px 8px rgba(0, 0, 0, 0.35);transition:transform .15s ease, box-shadow .15s ease;cursor:grab}
|
|
27
|
+
.before-after__handle-knob svg{width:1.1rem;height:1.1rem}
|
|
28
|
+
.before-after--dragging .before-after__handle-knob{cursor:grabbing;transform:scale(1.12);box-shadow:0 4px 16px rgba(0, 0, 0, 0.45)}
|
|
29
|
+
[data-orientation="vertical"] .before-after__handle-knob svg{transform:rotate(90deg)}
|
|
30
|
+
</style>}
|
|
31
|
+
|
|
32
|
+
# Store in site data so layouts can access it
|
|
33
|
+
site.data['before_after_css'] = css_content
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -13,9 +13,6 @@ module Jekyll
|
|
|
13
13
|
attrs
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
# Dedup script: only emit the JS block once per page
|
|
17
|
-
BEFORE_AFTER_SCRIPT_FLAG = :before_after_script_emitted
|
|
18
|
-
|
|
19
16
|
class BeforeAfterTag < Liquid::Tag
|
|
20
17
|
def initialize(tag_name, markup, tokens)
|
|
21
18
|
super
|
|
@@ -23,170 +20,18 @@ module Jekyll
|
|
|
23
20
|
end
|
|
24
21
|
|
|
25
22
|
def render(context)
|
|
26
|
-
config = context.registers[:site]&.config&.dig("before_after") || {}
|
|
27
23
|
before = @attributes["before"] || ""
|
|
28
24
|
after = @attributes["after"] || ""
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
start = (@attributes["start"] || config["start"] || 50).to_i
|
|
32
|
-
orient = @attributes["orientation"] || config["orientation"] || "horizontal"
|
|
33
|
-
alt_b = @attributes["alt_before"] || ""
|
|
34
|
-
alt_a = @attributes["alt_after"] || ""
|
|
35
|
-
srcset_b = @attributes["srcset_before"] || ""
|
|
36
|
-
srcset_a = @attributes["srcset_after"] || ""
|
|
37
|
-
sizes = @attributes["sizes"] || "(max-width: 768px) 100vw, 100vw"
|
|
38
|
-
|
|
39
|
-
before_img = build_img(before, alt_b, srcset_b, sizes)
|
|
40
|
-
after_img = build_img(after, alt_a, srcset_a, sizes)
|
|
41
|
-
|
|
42
|
-
script_block = emit_script(context)
|
|
25
|
+
orient = @attributes["orientation"] || "horizontal"
|
|
26
|
+
direction = orient == "vertical" ? ' direction="vertical"' : ""
|
|
43
27
|
|
|
44
28
|
<<~HTML
|
|
45
|
-
<
|
|
46
|
-
<
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
</div>
|
|
50
|
-
<div class="before-after__after">
|
|
51
|
-
#{after_img}
|
|
52
|
-
#{a_label.empty? ? "" : %(<span class="before-after__label before-after__label--after">#{a_label}</span>)}
|
|
53
|
-
</div>
|
|
54
|
-
<div class="before-after__handle"
|
|
55
|
-
role="slider"
|
|
56
|
-
aria-label="Image comparison slider"
|
|
57
|
-
aria-valuenow="#{start}"
|
|
58
|
-
aria-valuemin="0"
|
|
59
|
-
aria-valuemax="100"
|
|
60
|
-
tabindex="0">
|
|
61
|
-
<div class="before-after__handle-line"></div>
|
|
62
|
-
<div class="before-after__handle-knob">
|
|
63
|
-
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
|
|
64
|
-
<path d="M8 5l-5 7 5 7M16 5l5 7-5 7" stroke="currentColor" stroke-width="2"
|
|
65
|
-
stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
|
66
|
-
</svg>
|
|
67
|
-
</div>
|
|
68
|
-
</div>
|
|
69
|
-
</div>
|
|
70
|
-
#{script_block}
|
|
29
|
+
<img-comparison-slider#{direction}>
|
|
30
|
+
<img slot="first" src="#{before}" alt="Before">
|
|
31
|
+
<img slot="second" src="#{after}" alt="After">
|
|
32
|
+
</img-comparison-slider>
|
|
71
33
|
HTML
|
|
72
34
|
end
|
|
73
|
-
|
|
74
|
-
private
|
|
75
|
-
|
|
76
|
-
def build_img(src, alt, srcset, sizes)
|
|
77
|
-
if srcset && !srcset.empty?
|
|
78
|
-
%(<picture><source srcset="#{srcset}" sizes="#{sizes}"><img src="#{src}" alt="#{alt}" loading="lazy" draggable="false"></picture>)
|
|
79
|
-
else
|
|
80
|
-
%(<img src="#{src}" alt="#{alt}" loading="lazy" draggable="false">)
|
|
81
|
-
end
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
def emit_script(context)
|
|
85
|
-
return "" if context.registers[BEFORE_AFTER_SCRIPT_FLAG]
|
|
86
|
-
context.registers[BEFORE_AFTER_SCRIPT_FLAG] = true
|
|
87
|
-
<<~JS
|
|
88
|
-
<script>
|
|
89
|
-
(function() {
|
|
90
|
-
function initSlider(el) {
|
|
91
|
-
var orientation = el.dataset.orientation === "vertical" ? "vertical" : "horizontal";
|
|
92
|
-
var handle = el.querySelector(".before-after__handle");
|
|
93
|
-
var beforePane = el.querySelector(".before-after__before");
|
|
94
|
-
var start = parseFloat(el.dataset.start) || 50;
|
|
95
|
-
var dragging = false;
|
|
96
|
-
|
|
97
|
-
function setPosition(pct) {
|
|
98
|
-
pct = Math.min(100, Math.max(0, pct));
|
|
99
|
-
if (orientation === "vertical") {
|
|
100
|
-
beforePane.style.clipPath = "inset(0 0 " + (100 - pct) + "% 0)";
|
|
101
|
-
handle.style.top = pct + "%";
|
|
102
|
-
handle.style.left = "";
|
|
103
|
-
} else {
|
|
104
|
-
beforePane.style.clipPath = "inset(0 " + (100 - pct) + "% 0 0)";
|
|
105
|
-
handle.style.left = pct + "%";
|
|
106
|
-
handle.style.top = "";
|
|
107
|
-
}
|
|
108
|
-
handle.setAttribute("aria-valuenow", Math.round(pct));
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function pctFromEvent(e) {
|
|
112
|
-
var rect = el.getBoundingClientRect();
|
|
113
|
-
if (orientation === "vertical") {
|
|
114
|
-
return ((e.clientY - rect.top) / rect.height) * 100;
|
|
115
|
-
} else {
|
|
116
|
-
return ((e.clientX - rect.left) / rect.width) * 100;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
handle.addEventListener("pointerdown", function(e) {
|
|
121
|
-
dragging = true;
|
|
122
|
-
handle.setPointerCapture(e.pointerId);
|
|
123
|
-
el.classList.add("before-after--dragging");
|
|
124
|
-
e.preventDefault();
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
handle.addEventListener("pointermove", function(e) {
|
|
128
|
-
if (!dragging) return;
|
|
129
|
-
setPosition(pctFromEvent(e));
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
handle.addEventListener("pointerup", function() {
|
|
133
|
-
dragging = false;
|
|
134
|
-
el.classList.remove("before-after--dragging");
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
handle.addEventListener("pointercancel", function() {
|
|
138
|
-
dragging = false;
|
|
139
|
-
el.classList.remove("before-after--dragging");
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
// Keyboard support
|
|
143
|
-
handle.addEventListener("keydown", function(e) {
|
|
144
|
-
var step = e.shiftKey ? 1 : 5;
|
|
145
|
-
var cur = parseFloat(handle.getAttribute("aria-valuenow")) || 50;
|
|
146
|
-
if (e.key === "ArrowRight" || e.key === "ArrowDown") {
|
|
147
|
-
setPosition(cur + step);
|
|
148
|
-
e.preventDefault();
|
|
149
|
-
} else if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
|
|
150
|
-
setPosition(cur - step);
|
|
151
|
-
e.preventDefault();
|
|
152
|
-
}
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
// Click anywhere on container to jump
|
|
156
|
-
el.addEventListener("pointerdown", function(e) {
|
|
157
|
-
if (e.target === handle || handle.contains(e.target)) return;
|
|
158
|
-
setPosition(pctFromEvent(e));
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
// Recalculate on resize
|
|
162
|
-
if (window.ResizeObserver) {
|
|
163
|
-
new ResizeObserver(function() {
|
|
164
|
-
var cur = parseFloat(handle.getAttribute("aria-valuenow")) || start;
|
|
165
|
-
setPosition(cur);
|
|
166
|
-
}).observe(el);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
setPosition(start);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
function init() {
|
|
173
|
-
document.querySelectorAll(".before-after").forEach(function(el) {
|
|
174
|
-
if (!el.dataset.baInit) {
|
|
175
|
-
el.dataset.baInit = "1";
|
|
176
|
-
initSlider(el);
|
|
177
|
-
}
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
if (document.readyState === "loading") {
|
|
182
|
-
document.addEventListener("DOMContentLoaded", init);
|
|
183
|
-
} else {
|
|
184
|
-
init();
|
|
185
|
-
}
|
|
186
|
-
})();
|
|
187
|
-
</script>
|
|
188
|
-
JS
|
|
189
|
-
end
|
|
190
35
|
end
|
|
191
36
|
end
|
|
192
37
|
|
data/lib/jekyll-before-after.rb
CHANGED
|
@@ -1,3 +1,28 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "jekyll/before_after_tag"
|
|
4
|
+
|
|
5
|
+
module Jekyll
|
|
6
|
+
class BeforeAfterScriptInjector
|
|
7
|
+
def call(doc)
|
|
8
|
+
return if doc.output.nil? || !doc.output.include?("<img-comparison-slider>")
|
|
9
|
+
return if doc.output.include?("<!-- img-comparison-slider-injected -->")
|
|
10
|
+
|
|
11
|
+
script = <<~HTML
|
|
12
|
+
<!-- img-comparison-slider-injected -->
|
|
13
|
+
<script type="module" src="https://unpkg.com/img-comparison-slider@7/dist/index.js"></script>
|
|
14
|
+
<link rel="stylesheet" href="https://unpkg.com/img-comparison-slider@7/dist/styles.css">
|
|
15
|
+
HTML
|
|
16
|
+
|
|
17
|
+
doc.output.sub!("</head>", "#{script}</head>")
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
Jekyll::Hooks.register :pages, :post_render do |doc|
|
|
22
|
+
Jekyll::BeforeAfterScriptInjector.new.call(doc)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
Jekyll::Hooks.register :documents, :post_render do |doc|
|
|
26
|
+
Jekyll::BeforeAfterScriptInjector.new.call(doc)
|
|
27
|
+
end
|
|
28
|
+
end
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: jekyll-before-after
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jason Chance
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-08-
|
|
10
|
+
date: 2026-08-29 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: jekyll
|
|
@@ -44,9 +44,9 @@ files:
|
|
|
44
44
|
- CONTRIBUTING.md
|
|
45
45
|
- LICENSE.txt
|
|
46
46
|
- README.md
|
|
47
|
-
- _before-after.scss
|
|
48
47
|
- jekyll-before-after.gemspec
|
|
49
48
|
- lib/jekyll-before-after.rb
|
|
49
|
+
- lib/jekyll/before_after_generator.rb
|
|
50
50
|
- lib/jekyll/before_after_tag.rb
|
|
51
51
|
homepage: https://jasonchance.com/projects/jekyll-before-after/
|
|
52
52
|
licenses:
|
data/_before-after.scss
DELETED
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
// jekyll-before-after — copy to your site's CSS/SCSS directory and @import or @use it.
|
|
2
|
-
|
|
3
|
-
.before-after {
|
|
4
|
-
position: relative;
|
|
5
|
-
overflow: hidden;
|
|
6
|
-
cursor: col-resize;
|
|
7
|
-
user-select: none;
|
|
8
|
-
touch-action: none;
|
|
9
|
-
border-radius: var(--ba-radius, 4px);
|
|
10
|
-
background: var(--ba-bg, #000);
|
|
11
|
-
|
|
12
|
-
&[data-orientation="vertical"] {
|
|
13
|
-
cursor: row-resize;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
&--dragging {
|
|
17
|
-
cursor: grabbing;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// After pane: normal flow — its img drives the container height
|
|
22
|
-
.before-after__after {
|
|
23
|
-
position: relative;
|
|
24
|
-
display: block;
|
|
25
|
-
|
|
26
|
-
img,
|
|
27
|
-
picture img {
|
|
28
|
-
display: block;
|
|
29
|
-
width: 100%;
|
|
30
|
-
height: auto;
|
|
31
|
-
pointer-events: none;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Before pane: absolutely stacked on top, clipped by JS
|
|
36
|
-
.before-after__before {
|
|
37
|
-
position: absolute;
|
|
38
|
-
inset: 0;
|
|
39
|
-
z-index: 1;
|
|
40
|
-
clip-path: inset(0 50% 0 0); // overridden by JS on init
|
|
41
|
-
|
|
42
|
-
img,
|
|
43
|
-
picture img {
|
|
44
|
-
display: block;
|
|
45
|
-
width: 100%;
|
|
46
|
-
height: 100%;
|
|
47
|
-
object-fit: cover;
|
|
48
|
-
pointer-events: none;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Labels
|
|
53
|
-
.before-after__label {
|
|
54
|
-
position: absolute;
|
|
55
|
-
bottom: 0.75rem;
|
|
56
|
-
z-index: 2;
|
|
57
|
-
padding: 0.25rem 0.6rem;
|
|
58
|
-
font-size: 0.75rem;
|
|
59
|
-
font-weight: 600;
|
|
60
|
-
letter-spacing: 0.04em;
|
|
61
|
-
text-transform: uppercase;
|
|
62
|
-
background: var(--ba-label-bg, rgba(0, 0, 0, 0.55));
|
|
63
|
-
color: var(--ba-label-color, #fff);
|
|
64
|
-
border-radius: var(--ba-label-radius, 3px);
|
|
65
|
-
pointer-events: none;
|
|
66
|
-
|
|
67
|
-
&--before {
|
|
68
|
-
left: 0.75rem;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
&--after {
|
|
72
|
-
right: 0.75rem;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Handle
|
|
77
|
-
.before-after__handle {
|
|
78
|
-
position: absolute;
|
|
79
|
-
top: 50%;
|
|
80
|
-
left: 50%; // overridden by JS
|
|
81
|
-
transform: translate(-50%, -50%);
|
|
82
|
-
z-index: 10;
|
|
83
|
-
display: flex;
|
|
84
|
-
align-items: center;
|
|
85
|
-
justify-content: center;
|
|
86
|
-
outline: none;
|
|
87
|
-
|
|
88
|
-
&:focus-visible .before-after__handle-knob {
|
|
89
|
-
box-shadow: 0 0 0 3px var(--ba-focus-ring, rgba(255, 255, 255, 0.8));
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
.before-after__handle-line {
|
|
94
|
-
position: absolute;
|
|
95
|
-
background: var(--ba-handle-color, #fff);
|
|
96
|
-
opacity: 0.85;
|
|
97
|
-
|
|
98
|
-
.before-after:not([data-orientation="vertical"]) & {
|
|
99
|
-
width: var(--ba-line-width, 2px);
|
|
100
|
-
top: -200%;
|
|
101
|
-
bottom: -200%;
|
|
102
|
-
left: 50%;
|
|
103
|
-
transform: translateX(-50%);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
[data-orientation="vertical"] & {
|
|
107
|
-
height: var(--ba-line-width, 2px);
|
|
108
|
-
left: -200%;
|
|
109
|
-
right: -200%;
|
|
110
|
-
top: 50%;
|
|
111
|
-
transform: translateY(-50%);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
.before-after__handle-knob {
|
|
116
|
-
position: relative;
|
|
117
|
-
z-index: 1;
|
|
118
|
-
width: var(--ba-knob-size, 2.75rem);
|
|
119
|
-
height: var(--ba-knob-size, 2.75rem);
|
|
120
|
-
border-radius: 50%;
|
|
121
|
-
background: var(--ba-handle-color, #fff);
|
|
122
|
-
color: var(--ba-knob-icon-color, #333);
|
|
123
|
-
display: flex;
|
|
124
|
-
align-items: center;
|
|
125
|
-
justify-content: center;
|
|
126
|
-
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.35);
|
|
127
|
-
transition: transform 0.15s ease, box-shadow 0.15s ease;
|
|
128
|
-
cursor: grab;
|
|
129
|
-
|
|
130
|
-
svg {
|
|
131
|
-
width: 1.1rem;
|
|
132
|
-
height: 1.1rem;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
.before-after--dragging & {
|
|
136
|
-
cursor: grabbing;
|
|
137
|
-
transform: scale(1.12);
|
|
138
|
-
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.45);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
[data-orientation="vertical"] & svg {
|
|
142
|
-
transform: rotate(90deg);
|
|
143
|
-
}
|
|
144
|
-
}
|