mirror of
https://github.com/shoelace-style/webawesome.git
synced 2026-01-12 04:09:12 +00:00
[Image-comparer] Several fixes + rename to comparer (#883)
Co-authored-by: Cory LaViska <cory@abeautifulsite.net>
This commit is contained in:
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
@@ -1,14 +1,16 @@
|
||||
---
|
||||
title: Image Comparer
|
||||
description: Compare visual differences between similar photos with a sliding panel.
|
||||
title: Comparer
|
||||
description: Compare visual differences between similar content with a sliding panel.
|
||||
tags: [imagery, niche]
|
||||
icon: image-comparer
|
||||
icon: comparer
|
||||
---
|
||||
|
||||
For best results, use images that share the same dimensions. The slider can be controlled by dragging or pressing the left and right arrow keys. (Tip: press shift + arrows to move the slider in larger intervals, or home + end to jump to the beginning or end.)
|
||||
This is especially useful for comparing images, but can be used for comparing any type of content (for an example of using it to compare entire UIs, check out our [theme pages](/docs/themes/default/)).
|
||||
For best results, use content that shares the same dimensions.
|
||||
The slider can be controlled by dragging or pressing the left and right arrow keys. (Tip: press shift + arrows to move the slider in larger intervals, or home + end to jump to the beginning or end.)
|
||||
|
||||
```html {.example}
|
||||
<wa-image-comparer>
|
||||
<wa-comparer>
|
||||
<img
|
||||
slot="before"
|
||||
src="https://images.unsplash.com/photo-1517331156700-3c241d2b4d83?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=80&sat=-100&bri=-5"
|
||||
@@ -19,7 +21,7 @@ For best results, use images that share the same dimensions. The slider can be c
|
||||
src="https://images.unsplash.com/photo-1517331156700-3c241d2b4d83?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=80"
|
||||
alt="Color version of kittens in a basket looking around."
|
||||
/>
|
||||
</wa-image-comparer>
|
||||
</wa-comparer>
|
||||
```
|
||||
|
||||
## Examples
|
||||
@@ -29,7 +31,7 @@ For best results, use images that share the same dimensions. The slider can be c
|
||||
Use the `position` attribute to set the initial position of the slider. This is a percentage from `0` to `100`.
|
||||
|
||||
```html {.example}
|
||||
<wa-image-comparer position="25">
|
||||
<wa-comparer position="25">
|
||||
<img
|
||||
slot="before"
|
||||
src="https://images.unsplash.com/photo-1520903074185-8eca362b3dce?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1200&q=80"
|
||||
@@ -40,5 +42,5 @@ Use the `position` attribute to set the initial position of the slider. This is
|
||||
src="https://images.unsplash.com/photo-1520640023173-50a135e35804?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2250&q=80"
|
||||
alt="A person sitting on a yellow curb tying shoelaces on a boot."
|
||||
/>
|
||||
</wa-image-comparer>
|
||||
</wa-comparer>
|
||||
```
|
||||
@@ -12,6 +12,10 @@ Components with the <wa-badge variant="warning" pill>Experimental</wa-badge> bad
|
||||
During the alpha period, things might break! We take breaking changes very seriously, but sometimes they're necessary to make the final product that much better. We appreciate your patience!
|
||||
:::
|
||||
|
||||
## Next
|
||||
|
||||
- 🚨 BREAKING: Renamed `<image-comparer>` to `<wa-comparer>` and improved compatibility for non-image content.
|
||||
|
||||
## 3.0.0-alpha.12
|
||||
|
||||
### Enhancements
|
||||
|
||||
4
docs/docs/themes/demo.njk
vendored
4
docs/docs/themes/demo.njk
vendored
@@ -26,14 +26,14 @@ eleventyComputed:
|
||||
{% include 'theme-showcase.njk' %}
|
||||
{% endset %}
|
||||
|
||||
<wa-image-comparer style="width: 100%" position="90">
|
||||
<wa-comparer style="width: 100%" position="90">
|
||||
<div slot="after" class="theme-showcase wa-gap-xl">
|
||||
{{ content | safe }}
|
||||
</div>
|
||||
<div slot="before" class="theme-showcase wa-gap-xl wa-invert">
|
||||
{{ content | safe }}
|
||||
</div>
|
||||
</wa-image-comparer>
|
||||
</wa-comparer>
|
||||
|
||||
<script type="module">
|
||||
import { urls as stylesheetURLs, docsURLs, icons } from "/assets/scripts/tweak/data.js";
|
||||
|
||||
@@ -6,9 +6,6 @@
|
||||
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.image-comparer {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
overflow: hidden;
|
||||
@@ -24,6 +21,10 @@
|
||||
max-width: 100% !important;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
&::slotted(:not(img, svg)) {
|
||||
isolation: isolate;
|
||||
}
|
||||
}
|
||||
|
||||
.after {
|
||||
@@ -1,18 +1,19 @@
|
||||
import { expect } from '@open-wc/testing';
|
||||
import { sendKeys } from '@web/test-runner-commands';
|
||||
import { html } from 'lit';
|
||||
import sinon from 'sinon';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import type WaImageComparer from './image-comparer.js';
|
||||
import type WaComparer from './comparer.js';
|
||||
|
||||
describe('<wa-image-comparer>', () => {
|
||||
describe('<wa-comparer>', () => {
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
it('should render a basic before/after', async () => {
|
||||
const el = await fixture<WaImageComparer>(html`
|
||||
<wa-image-comparer>
|
||||
const el = await fixture<WaComparer>(html`
|
||||
<wa-comparer>
|
||||
<div slot="before"></div>
|
||||
<div slot="after"></div>
|
||||
</wa-image-comparer>
|
||||
</wa-comparer>
|
||||
`);
|
||||
|
||||
const afterPart = el.shadowRoot!.querySelector<HTMLElement>('[part~="after"]')!;
|
||||
@@ -29,11 +30,11 @@ describe('<wa-image-comparer>', () => {
|
||||
});
|
||||
|
||||
it('should emit change event when position changed manually', async () => {
|
||||
const el = await fixture<WaImageComparer>(html`
|
||||
<wa-image-comparer>
|
||||
const el = await fixture<WaComparer>(html`
|
||||
<wa-comparer>
|
||||
<div slot="before"></div>
|
||||
<div slot="after"></div>
|
||||
</wa-image-comparer>
|
||||
</wa-comparer>
|
||||
`);
|
||||
const handler = sinon.spy();
|
||||
|
||||
@@ -46,194 +47,166 @@ describe('<wa-image-comparer>', () => {
|
||||
});
|
||||
|
||||
it('should increment position on arrow right', async () => {
|
||||
const el = await fixture<WaImageComparer>(html`
|
||||
<wa-image-comparer>
|
||||
const el = await fixture<WaComparer>(html`
|
||||
<wa-comparer>
|
||||
<div slot="before"></div>
|
||||
<div slot="after"></div>
|
||||
</wa-image-comparer>
|
||||
</wa-comparer>
|
||||
`);
|
||||
|
||||
const base = el.shadowRoot!.querySelector<HTMLElement>('[part~="base"]')!;
|
||||
|
||||
base.dispatchEvent(
|
||||
new KeyboardEvent('keydown', {
|
||||
key: 'ArrowRight',
|
||||
}),
|
||||
);
|
||||
const handle = el.shadowRoot!.querySelector<HTMLElement>('[part~="handle"]')!;
|
||||
handle.focus();
|
||||
await sendKeys({ press: 'ArrowRight' });
|
||||
await el.updateComplete;
|
||||
|
||||
expect(el.position).to.equal(51);
|
||||
});
|
||||
|
||||
it('should decrement position on arrow left', async () => {
|
||||
const el = await fixture<WaImageComparer>(html`
|
||||
<wa-image-comparer>
|
||||
const el = await fixture<WaComparer>(html`
|
||||
<wa-comparer>
|
||||
<div slot="before"></div>
|
||||
<div slot="after"></div>
|
||||
</wa-image-comparer>
|
||||
</wa-comparer>
|
||||
`);
|
||||
|
||||
const base = el.shadowRoot!.querySelector<HTMLElement>('[part~="base"]')!;
|
||||
const handle = el.shadowRoot!.querySelector<HTMLElement>('[part~="handle"]')!;
|
||||
|
||||
base.dispatchEvent(
|
||||
new KeyboardEvent('keydown', {
|
||||
key: 'ArrowLeft',
|
||||
}),
|
||||
);
|
||||
handle.focus();
|
||||
await sendKeys({ press: 'ArrowLeft' });
|
||||
await el.updateComplete;
|
||||
|
||||
expect(el.position).to.equal(49);
|
||||
});
|
||||
|
||||
it('should set position to 0 on home key', async () => {
|
||||
const el = await fixture<WaImageComparer>(html`
|
||||
<wa-image-comparer>
|
||||
const el = await fixture<WaComparer>(html`
|
||||
<wa-comparer>
|
||||
<div slot="before"></div>
|
||||
<div slot="after"></div>
|
||||
</wa-image-comparer>
|
||||
</wa-comparer>
|
||||
`);
|
||||
|
||||
const base = el.shadowRoot!.querySelector<HTMLElement>('[part~="base"]')!;
|
||||
const handle = el.shadowRoot!.querySelector<HTMLElement>('[part~="handle"]')!;
|
||||
|
||||
base.dispatchEvent(
|
||||
new KeyboardEvent('keydown', {
|
||||
key: 'Home',
|
||||
}),
|
||||
);
|
||||
handle.focus();
|
||||
await sendKeys({ press: 'Home' });
|
||||
await el.updateComplete;
|
||||
|
||||
expect(el.position).to.equal(0);
|
||||
});
|
||||
|
||||
it('should set position to 100 on end key', async () => {
|
||||
const el = await fixture<WaImageComparer>(html`
|
||||
<wa-image-comparer>
|
||||
const el = await fixture<WaComparer>(html`
|
||||
<wa-comparer>
|
||||
<div slot="before"></div>
|
||||
<div slot="after"></div>
|
||||
</wa-image-comparer>
|
||||
</wa-comparer>
|
||||
`);
|
||||
|
||||
const base = el.shadowRoot!.querySelector<HTMLElement>('[part~="base"]')!;
|
||||
const handle = el.shadowRoot!.querySelector<HTMLElement>('[part~="handle"]')!;
|
||||
|
||||
base.dispatchEvent(
|
||||
new KeyboardEvent('keydown', {
|
||||
key: 'End',
|
||||
}),
|
||||
);
|
||||
handle.focus();
|
||||
await sendKeys({ press: 'End' });
|
||||
await el.updateComplete;
|
||||
|
||||
expect(el.position).to.equal(100);
|
||||
});
|
||||
|
||||
it('should clamp to 100 on arrow right', async () => {
|
||||
const el = await fixture<WaImageComparer>(html`
|
||||
<wa-image-comparer>
|
||||
const el = await fixture<WaComparer>(html`
|
||||
<wa-comparer>
|
||||
<div slot="before"></div>
|
||||
<div slot="after"></div>
|
||||
</wa-image-comparer>
|
||||
</wa-comparer>
|
||||
`);
|
||||
|
||||
el.position = 0;
|
||||
await el.updateComplete;
|
||||
|
||||
const base = el.shadowRoot!.querySelector<HTMLElement>('[part~="base"]')!;
|
||||
const handle = el.shadowRoot!.querySelector<HTMLElement>('[part~="handle"]')!;
|
||||
|
||||
base.dispatchEvent(
|
||||
new KeyboardEvent('keydown', {
|
||||
key: 'ArrowLeft',
|
||||
}),
|
||||
);
|
||||
handle.focus();
|
||||
await sendKeys({ press: 'ArrowLeft' });
|
||||
await el.updateComplete;
|
||||
|
||||
expect(el.position).to.equal(0);
|
||||
});
|
||||
|
||||
it('should clamp to 0 on arrow left', async () => {
|
||||
const el = await fixture<WaImageComparer>(html`
|
||||
<wa-image-comparer>
|
||||
const el = await fixture<WaComparer>(html`
|
||||
<wa-comparer>
|
||||
<div slot="before"></div>
|
||||
<div slot="after"></div>
|
||||
</wa-image-comparer>
|
||||
</wa-comparer>
|
||||
`);
|
||||
|
||||
el.position = 100;
|
||||
await el.updateComplete;
|
||||
|
||||
const base = el.shadowRoot!.querySelector<HTMLElement>('[part~="base"]')!;
|
||||
const handle = el.shadowRoot!.querySelector<HTMLElement>('[part~="handle"]')!;
|
||||
|
||||
base.dispatchEvent(
|
||||
new KeyboardEvent('keydown', {
|
||||
key: 'ArrowRight',
|
||||
}),
|
||||
);
|
||||
handle.focus();
|
||||
await sendKeys({ press: 'ArrowRight' });
|
||||
await el.updateComplete;
|
||||
|
||||
expect(el.position).to.equal(100);
|
||||
});
|
||||
|
||||
it('should increment position by 10 on arrow right + shift', async () => {
|
||||
const el = await fixture<WaImageComparer>(html`
|
||||
<wa-image-comparer>
|
||||
const el = await fixture<WaComparer>(html`
|
||||
<wa-comparer>
|
||||
<div slot="before"></div>
|
||||
<div slot="after"></div>
|
||||
</wa-image-comparer>
|
||||
</wa-comparer>
|
||||
`);
|
||||
|
||||
const base = el.shadowRoot!.querySelector<HTMLElement>('[part~="base"]')!;
|
||||
const handle = el.shadowRoot!.querySelector<HTMLElement>('[part~="handle"]')!;
|
||||
|
||||
base.dispatchEvent(
|
||||
new KeyboardEvent('keydown', {
|
||||
key: 'ArrowRight',
|
||||
shiftKey: true,
|
||||
}),
|
||||
);
|
||||
handle.focus();
|
||||
await sendKeys({ press: 'Shift+ArrowRight' });
|
||||
await el.updateComplete;
|
||||
|
||||
expect(el.position).to.equal(60);
|
||||
});
|
||||
|
||||
it('should decrement position by 10 on arrow left + shift', async () => {
|
||||
const el = await fixture<WaImageComparer>(html`
|
||||
<wa-image-comparer>
|
||||
const el = await fixture<WaComparer>(html`
|
||||
<wa-comparer>
|
||||
<div slot="before"></div>
|
||||
<div slot="after"></div>
|
||||
</wa-image-comparer>
|
||||
</wa-comparer>
|
||||
`);
|
||||
|
||||
const base = el.shadowRoot!.querySelector<HTMLElement>('[part~="base"]')!;
|
||||
const handle = el.shadowRoot!.querySelector<HTMLElement>('[part~="handle"]')!;
|
||||
|
||||
base.dispatchEvent(
|
||||
new KeyboardEvent('keydown', {
|
||||
key: 'ArrowLeft',
|
||||
shiftKey: true,
|
||||
}),
|
||||
);
|
||||
handle.focus();
|
||||
await sendKeys({ press: 'Shift+ArrowLeft' });
|
||||
await el.updateComplete;
|
||||
|
||||
expect(el.position).to.equal(40);
|
||||
});
|
||||
|
||||
it('should set position by attribute', async () => {
|
||||
const el = await fixture<WaImageComparer>(html`
|
||||
<wa-image-comparer position="10">
|
||||
const el = await fixture<WaComparer>(html`
|
||||
<wa-comparer position="10">
|
||||
<div slot="before"></div>
|
||||
<div slot="after"></div>
|
||||
</wa-image-comparer>
|
||||
</wa-comparer>
|
||||
`);
|
||||
|
||||
expect(el.position).to.equal(10);
|
||||
});
|
||||
|
||||
it('should move position on drag', async () => {
|
||||
const el = await fixture<WaImageComparer>(html`
|
||||
<wa-image-comparer>
|
||||
const el = await fixture<WaComparer>(html`
|
||||
<wa-comparer>
|
||||
<div slot="before" style="width: 50px"></div>
|
||||
<div slot="after" style="width: 50px"></div>
|
||||
</wa-image-comparer>
|
||||
</wa-comparer>
|
||||
`);
|
||||
const handle = el.shadowRoot!.querySelector<HTMLElement>('[part~="handle"]')!;
|
||||
const base = el.shadowRoot!.querySelector<HTMLElement>('[part~="base"]')!;
|
||||
const rect = base.getBoundingClientRect();
|
||||
const rect = handle.getBoundingClientRect();
|
||||
const offsetX = rect.left + window.pageXOffset;
|
||||
const offsetY = rect.top + window.pageYOffset;
|
||||
|
||||
@@ -241,7 +214,7 @@ describe('<wa-image-comparer>', () => {
|
||||
|
||||
document.dispatchEvent(
|
||||
new PointerEvent('pointermove', {
|
||||
clientX: offsetX + 20,
|
||||
clientX: offsetX + 15,
|
||||
clientY: offsetY,
|
||||
}),
|
||||
);
|
||||
@@ -7,52 +7,50 @@ import { watch } from '../../internal/watch.js';
|
||||
import WebAwesomeElement from '../../internal/webawesome-element.js';
|
||||
import { LocalizeController } from '../../utilities/localize.js';
|
||||
import '../icon/icon.js';
|
||||
import styles from './image-comparer.css';
|
||||
import styles from './comparer.css';
|
||||
|
||||
/**
|
||||
* @summary Compare visual differences between similar photos with a sliding panel.
|
||||
* @documentation https://backers.webawesome.com/docs/components/image-comparer
|
||||
* @summary Compare visual differences between similar content with a sliding panel.
|
||||
* @documentation https://backers.webawesome.com/docs/components/comparer
|
||||
* @status stable
|
||||
* @since 2.0
|
||||
*
|
||||
* @dependency wa-icon
|
||||
*
|
||||
* @slot before - The before image, an `<img>` or `<svg>` element.
|
||||
* @slot after - The after image, an `<img>` or `<svg>` element.
|
||||
* @slot before - The before content, often an `<img>` or `<svg>` element.
|
||||
* @slot after - The after content, often an `<img>` or `<svg>` element.
|
||||
* @slot handle - The icon used inside the handle.
|
||||
*
|
||||
* @event change - Emitted when the position changes.
|
||||
*
|
||||
* @csspart base - The component's base wrapper.
|
||||
* @csspart before - The container that wraps the before image.
|
||||
* @csspart after - The container that wraps the after image.
|
||||
* @csspart divider - The divider that separates the images.
|
||||
* @csspart handle - The handle that the user drags to expose the after image.
|
||||
* @csspart before - The container that wraps the before content.
|
||||
* @csspart after - The container that wraps the after content.
|
||||
* @csspart divider - The divider that separates the before and after content.
|
||||
* @csspart handle - The handle that the user drags to expose the after content.
|
||||
*
|
||||
* @cssproperty --divider-color - The color of the divider.
|
||||
* @cssproperty --divider-width - The width of the dividing line.
|
||||
* @cssproperty --handle-color - The color of the icon used inside the handle.
|
||||
* @cssproperty --handle-size - The size of the compare handle.
|
||||
*/
|
||||
@customElement('wa-image-comparer')
|
||||
export default class WaImageComparer extends WebAwesomeElement {
|
||||
@customElement('wa-comparer')
|
||||
export default class WaComparer extends WebAwesomeElement {
|
||||
static shadowStyle = styles;
|
||||
|
||||
private readonly localize = new LocalizeController(this);
|
||||
|
||||
@query('.image-comparer') base: HTMLElement;
|
||||
@query('.handle') handle: HTMLElement;
|
||||
|
||||
/** The position of the divider as a percentage. */
|
||||
@property({ type: Number, reflect: true }) position = 50;
|
||||
|
||||
private handleDrag(event: PointerEvent) {
|
||||
const { width } = this.base.getBoundingClientRect();
|
||||
const { width } = this.getBoundingClientRect();
|
||||
const isRtl = this.localize.dir() === 'rtl';
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
drag(this.base, {
|
||||
drag(this, {
|
||||
onMove: x => {
|
||||
this.position = parseFloat(clamp((x / width) * 100, 0, 100).toFixed(2));
|
||||
if (isRtl) this.position = 100 - this.position;
|
||||
@@ -98,46 +96,45 @@ export default class WaImageComparer extends WebAwesomeElement {
|
||||
const isRtl = this.hasUpdated ? this.localize.dir() === 'rtl' : this.dir === 'rtl';
|
||||
|
||||
return html`
|
||||
<div part="base" id="image-comparer" class="image-comparer" @keydown=${this.handleKeyDown}>
|
||||
<div class="image">
|
||||
<div part="before" class="before">
|
||||
<slot name="before"></slot>
|
||||
</div>
|
||||
|
||||
<div
|
||||
part="after"
|
||||
class="after"
|
||||
style=${styleMap({
|
||||
clipPath: isRtl ? `inset(0 0 0 ${100 - this.position}%)` : `inset(0 ${100 - this.position}% 0 0)`,
|
||||
})}
|
||||
>
|
||||
<slot name="after"></slot>
|
||||
</div>
|
||||
<div class="image">
|
||||
<div part="before" class="before">
|
||||
<slot name="before"></slot>
|
||||
</div>
|
||||
|
||||
<div
|
||||
part="divider"
|
||||
class="divider"
|
||||
part="after"
|
||||
class="after"
|
||||
style=${styleMap({
|
||||
left: isRtl ? `${100 - this.position}%` : `${this.position}%`,
|
||||
clipPath: isRtl ? `inset(0 0 0 ${100 - this.position}%)` : `inset(0 ${100 - this.position}% 0 0)`,
|
||||
})}
|
||||
@mousedown=${this.handleDrag}
|
||||
@touchstart=${this.handleDrag}
|
||||
>
|
||||
<div
|
||||
part="handle"
|
||||
class="handle"
|
||||
role="scrollbar"
|
||||
aria-valuenow=${this.position}
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
aria-controls="image-comparer"
|
||||
tabindex="0"
|
||||
>
|
||||
<slot name="handle">
|
||||
<wa-icon library="system" name="grip-vertical" variant="solid"></wa-icon>
|
||||
</slot>
|
||||
</div>
|
||||
<slot name="after"></slot>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
part="divider"
|
||||
class="divider"
|
||||
style=${styleMap({
|
||||
left: isRtl ? `${100 - this.position}%` : `${this.position}%`,
|
||||
})}
|
||||
@keydown=${this.handleKeyDown}
|
||||
@mousedown=${this.handleDrag}
|
||||
@touchstart=${this.handleDrag}
|
||||
>
|
||||
<div
|
||||
part="handle"
|
||||
class="handle"
|
||||
role="scrollbar"
|
||||
aria-valuenow=${this.position}
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
aria-controls="comparer"
|
||||
tabindex="0"
|
||||
>
|
||||
<slot name="handle">
|
||||
<wa-icon library="system" name="grip-vertical" variant="solid"></wa-icon>
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -146,6 +143,6 @@ export default class WaImageComparer extends WebAwesomeElement {
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'wa-image-comparer': WaImageComparer;
|
||||
'wa-comparer': WaComparer;
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,7 @@
|
||||
}
|
||||
|
||||
wa-carousel::part(pagination-item),
|
||||
wa-image-comparer::part(handle),
|
||||
wa-comparer::part(handle),
|
||||
wa-progress-bar::part(base),
|
||||
wa-slider::part(base),
|
||||
input[type='range'],
|
||||
|
||||
Reference in New Issue
Block a user