├── .gitignore
├── netlify.toml
├── static
├── javaskipped-a.png
└── javaskipped-b.png
├── _components
├── visually-hidden.css
├── image-compare-defined.webc
├── image-compare-hidden.webc
├── image-compare-disabled.webc
├── image-compare-enabled.webc
├── image-compare-shadowdom.webc
└── image-compare-nojs.webc
├── eleventy.config.js
├── package.json
├── LICENSE
├── _includes
└── layout.webc
├── README.md
└── index.webc
/.gitignore:
--------------------------------------------------------------------------------
1 | _site
2 | node_modules
3 | package-lock.json
--------------------------------------------------------------------------------
/netlify.toml:
--------------------------------------------------------------------------------
1 | [build]
2 | publish = "_site"
3 | command = "npm run build"
4 |
--------------------------------------------------------------------------------
/static/javaskipped-a.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/11ty/demo-webc-image-compare/main/static/javaskipped-a.png
--------------------------------------------------------------------------------
/static/javaskipped-b.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/11ty/demo-webc-image-compare/main/static/javaskipped-b.png
--------------------------------------------------------------------------------
/_components/visually-hidden.css:
--------------------------------------------------------------------------------
1 | .visually-hidden {
2 | overflow: hidden;
3 | position: absolute;
4 | white-space: nowrap;
5 | width: 1px;
6 | height: 1px;
7 | }
--------------------------------------------------------------------------------
/eleventy.config.js:
--------------------------------------------------------------------------------
1 | const pluginWebc = require("@11ty/eleventy-plugin-webc");
2 | const syntaxHighlight = require("@11ty/eleventy-plugin-syntaxhighlight");
3 |
4 | module.exports = function(eleventyConfig) {
5 | eleventyConfig.ignores.add("README.md");
6 |
7 | eleventyConfig.addPlugin(syntaxHighlight);
8 | eleventyConfig.addPlugin(pluginWebc, {
9 | components: [
10 | "./_components/**/*.webc",
11 | "npm:@11ty/is-land/*.webc",
12 | "npm:@11ty/eleventy-plugin-syntaxhighlight/*.webc",
13 | ],
14 | });
15 |
16 | eleventyConfig.setServerPassthroughCopyBehavior("copy");
17 | eleventyConfig.addPassthroughCopy({
18 | "static/*": "/",
19 | "node_modules/@11ty/is-land/is-land.js": "/is-land.js"
20 | });
21 |
22 | eleventyConfig.setServerOptions({
23 | domdiff: false
24 | });
25 | };
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@11ty/demo-webc-image-compare",
3 | "version": "1.0.0",
4 | "description": "",
5 | "scripts": {
6 | "build": "npx @11ty/eleventy",
7 | "start": "npx @11ty/eleventy --serve --incremental"
8 | },
9 | "engines": {
10 | "node": ">=14"
11 | },
12 | "funding": {
13 | "type": "opencollective",
14 | "url": "https://opencollective.com/11ty"
15 | },
16 | "author": {
17 | "name": "Zach Leatherman",
18 | "email": "zachleatherman@gmail.com",
19 | "url": "https://zachleat.com/"
20 | },
21 | "repository": {
22 | "type": "git",
23 | "url": "git://github.com/11ty/demo-webc-image-compare.git"
24 | },
25 | "bugs": "https://github.com/11ty/demo-webc-image-compare/issues",
26 | "license": "MIT",
27 | "dependencies": {
28 | "@11ty/eleventy": "^2.0.0",
29 | "@11ty/eleventy-plugin-syntaxhighlight": "^4.2.0",
30 | "@11ty/eleventy-plugin-webc": "^0.11.0",
31 | "@11ty/is-land": "^4.0.0"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Zach Leatherman
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/_includes/layout.webc:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Eleventy WebC Progressive Enhancement Recipes
8 |
9 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Progressive Enhancement Recipes using Eleventy WebC Image Comparison Components
2 |
3 | A bunch of examples of progressively enhanced server-rendered image comparison web components using WebC.
4 |
5 | This component expects two images to be nested inside and will produce a input type range to let the user compare the two images by dragging the range slider.
6 |
7 | * [Live demo on Netlify](https://demo-webc-image-compare.netlify.app/)
8 | * [Deploy to Netlify](https://app.netlify.com/start/deploy?repository=https://github.com/11ty/demo-webc-image-compare)
9 | * [Learn more about WebC](https://www.11ty.dev/docs/languages/webc/)
10 | * Originally a demo for a talk given at the [Eleventy Meetup: Adding Components to WebC with Eleventy](https://www.zachleat.com/web/webc-in-eleventy/)
11 |
12 | ## Examples
13 |
14 | 1. [Always Enabled](https://demo-webc-image-compare.netlify.app/#enabled): form control is interactive pre-JS and synchronizes post-JS.
15 | 1. [Disabled until JavaScript](https://demo-webc-image-compare.netlify.app/#disabled): uses JS to toggle the range input from disabled to enabled.
16 | 1. [Hidden via `:not(:defined)` CSS](https://demo-webc-image-compare.netlify.app/#hidden-css)
17 | 1. [Hidden via JS ``](https://demo-webc-image-compare.netlify.app/#hidden-tmpl)
18 | 1. [Declarative Shadow DOM](https://demo-webc-image-compare.netlify.app/#dsd)
19 | 1. [`:has()` and Radios](https://demo-webc-image-compare.netlify.app/#has-radios)
20 | 1. [Bonus: `opacity` Slider](https://demo-webc-image-compare.netlify.app/#opacity)
--------------------------------------------------------------------------------
/_components/image-compare-defined.webc:
--------------------------------------------------------------------------------
1 |
2 |
The examples below are in order of implementation complexity from least to most. All are using is-land, specifically <is-land on:visible>. The pre-JS experience is emulated using <is-land on:media="(max-width: 0px)">.
11 |
12 |
Summary
13 |
14 |
15 |
If you want to show both images pre-JS, this can be solved with an entirely clientside Image Comparison component but it will negatively impact your Content Layout Shift score. This use case is not shown below.
16 |
Do you want to show either the left or right image pre-JS? Use @value="0" or @value="100" respectively.
17 |
Do you want the form control to be interactive pre-JS? Go to the Always Enabled approach.
18 |
If you want an interactive form control pre-JS and have it be functional too? See the :has() and Radios approach.
19 |
Does this form input need to be a successful form control and submit to the server? The ShadowDOM example is the only one that doesn’t work with forms. This *could* be fixed with JS but is it worth it?
This version uses JS to toggle the range input from disabled to enabled.
86 |
Disabling the control avoids the uncanny valley (in comparison to keeping it enabled) by communicating to the user that the control is not yet initialized.
This version uses Declarative Shadow DOM for the range input. Before JS, the range input is always enabled and synchronized to the display when JS runs.
223 |
When Declarative Shadow DOM is not supported, the polyfill is applied inline (it’s a few lines of code). Before JS, the range input is hidden.
224 |
Benefit: we can move some of the styles into Shadow DOM to scope those styles for free!
225 |
Drawback: This version does not have a successful form control because it uses Shadow DOM. This can be fixed with JS (but requires JS).
226 |
Drawback: Is there a way to de-duplicate the declarative shadow dom template without JavaScript? Does every instance of the component need the same nested markup?
This version uses :has and radios for a functional No-JS experience (and synchronizes the value pre-JS and post-JS). Of course, the number of radios shown can be customized.
276 |
If :has() is not supported (at time of writing, Firefox), the inputs are hidden (though you could modify this behavior to be Always Enabled and synchronized).
For fun you can change clip-path: inset(0 0 0 var(--position)); to opacity: var(--position); on any of these components to have the slider vary opacity instead of clip amount.