add animated-image

pull/572/head
Cory LaViska 2021-10-16 08:29:25 -04:00
rodzic ff84beaade
commit be662ddf32
10 zmienionych plików z 261 dodań i 0 usunięć

Wyświetl plik

@ -54,6 +54,7 @@
<!--plop:component-->
- Utilities
- [Animated Image](/components/animated-image)
- [Animation](/components/animation)
- [Format Bytes](/components/format-bytes)
- [Format Date](/components/format-date)

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 1.2 MiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 1.5 MiB

Wyświetl plik

@ -0,0 +1,63 @@
# Animated Image
[component-header:sl-animated-image]
A component for displaying animated GIFs and WEBPs that play and pause on interaction.
```html preview
<sl-animated-image
src="/assets/images/walk.gif"
alt="Animation of untied shoes walking on pavement"
></sl-animated-image>
```
## Examples
### WEBP Images
Both GIF and WEBP images are supported.
```html preview
<sl-animated-image
src="/assets/images/tie.webp"
alt="Animation of a shoe being tied"
></sl-animated-image>
```
### Setting a Width and Height
To set a custom size, apply a width and/or height to the host element.
```html preview
<sl-animated-image
src="/assets/images/walk.gif"
alt="Animation of untied shoes walking on pavement"
style="width: 150px; height: 200px;"
>
</sl-animated-image>
```
### Customizing the Control Box
You can change the appearance and location of the control box by targeting the `control-box` part in your styles.
```html preview
<sl-animated-image
src="/assets/images/walk.gif"
alt="Animation of untied shoes walking on pavement"
class="animated-image-custom-control-box"
></sl-animated-image>
<style>
.animated-image-custom-control-box::part(control-box) {
top: auto;
right: auto;
bottom: 1rem;
left: 1rem;
background-color: deeppink;
color: white;
}
</style>
```
[component-metadata:sl-animated-image]

Wyświetl plik

@ -8,6 +8,7 @@ _During the beta period, these restrictions may be relaxed in the event of a mis
## Next
- Added experimental `<sl-animated-image>` component
- Added `label` attribute to `<sl-progress-bar>` and `<sl-progress-ring>` to improve a11y
- Fixed a bug where the tooltip would show briefly when clicking a disabled `<sl-range>`
- Updated to Bootstrap Icons to 1.6.0

Wyświetl plik

@ -0,0 +1,52 @@
import { css } from 'lit';
import componentStyles from '../../styles/component.styles';
export default css`
${componentStyles}
:host {
--control-box-size: 2.5rem;
--icon-size: calc(var(--control-box-size) * 0.625);
display: inline-flex;
position: relative;
cursor: pointer;
}
img {
display: block;
width: 100%;
height: 100%;
}
img[aria-hidden='true'] {
display: none;
}
.animated-image__control-box {
display: flex;
position: absolute;
align-items: center;
justify-content: center;
top: calc(50% - var(--control-box-size) / 2);
right: calc(50% - var(--control-box-size) / 2);
width: var(--control-box-size);
height: var(--control-box-size);
font-size: var(--icon-size);
background: none;
border: none;
background-color: rgb(var(--sl-color-neutral-1000) / 50%);
border-radius: var(--sl-border-radius-circle);
color: rgb(var(--sl-color-neutral-0));
pointer-events: none;
transition: var(--sl-transition-fast) opacity;
}
:host([play]:hover) .animated-image__control-box {
opacity: 1;
transform: scale(1);
}
:host([play]:not(:hover)) .animated-image__control-box {
opacity: 0;
}
`;

Wyświetl plik

@ -0,0 +1,13 @@
import { expect, fixture, html, waitUntil } from '@open-wc/testing';
// import sinon from 'sinon';
import '../../../dist/shoelace.js';
import type SlAnimatedImage from './animated-image';
describe('<sl-animated-image>', () => {
it('should render a component', async () => {
const el = await fixture(html` <sl-animated-image></sl-animated-image> `);
expect(el).to.exist;
});
});

Wyświetl plik

@ -0,0 +1,120 @@
import { LitElement, html } from 'lit';
import { customElement, property, query, state } from 'lit/decorators.js';
import { watch } from '../../internal/watch';
import { emit } from '../../internal/event';
import styles from './animated-image.styles';
import '../icon/icon';
/**
* @since 2.0
* @status experimental
*
* @dependency sl-icon
*
* @event sl-load - Emitted when the image loads successfully.
* @event sl-error - Emitted when the image fails to load.
*
* @part - control-box - The container that surrounds the pause/play icons and provides their background.
* @part - play-icon - The icon to use for the play button.
* @part - pause-icon - The icon to use for the pause button.
*
* @cssproperty --control-box-size - The size of the icon box.
* @cssproperty --icon-size - The size of the play/pause icons.
*/
@customElement('sl-animated-image')
export default class SlAnimatedImage extends LitElement {
static styles = styles;
@state() frozenFrame: string;
@state() isLoaded = false;
@query('.animated-image__animated') animatedImage: HTMLImageElement;
/** The image's src attribute. */
@property() src: string;
/** The image's alt attribute. */
@property() alt: string;
/** When set, the image will animate. Otherwise, it will be paused. */
@property({ type: Boolean, reflect: true }) play: boolean;
handleClick() {
this.play = !this.play;
}
handleLoad() {
const canvas = document.createElement('canvas');
const { width, height } = this.animatedImage;
canvas.width = width;
canvas.height = height;
canvas.getContext('2d')!.drawImage(this.animatedImage, 0, 0, width, height);
this.frozenFrame = canvas.toDataURL('image/gif');
if (!this.isLoaded) {
emit(this, 'sl-load');
this.isLoaded = true;
}
}
handleError() {
emit(this, 'sl-error');
}
@watch('play')
async handlePlayChange() {
// When the animation starts playing, reset the src so it plays from the beginning. Since the src is cached, this
// won't trigger another request.
if (this.play) {
this.animatedImage.src = '';
this.animatedImage.src = this.src;
}
}
@watch('src')
handleSrcChange() {
this.isLoaded = false;
}
render() {
return html`
<div class="animated-image">
<img
class="animated-image__animated"
src=${this.src}
alt=${this.alt}
crossorigin="anonymous"
aria-hidden=${this.play ? 'false' : 'true'}
@click=${this.handleClick}
@load=${this.handleLoad}
@error=${this.handleError}
/>
${this.isLoaded
? html`
<img
class="animated-image__frozen"
src=${this.frozenFrame}
alt=${this.alt}
aria-hidden=${this.play ? 'true' : 'false'}
@click=${this.handleClick}
/>
<div part="control-box" class="animated-image__control-box">
${this.play
? html`<sl-icon part="pause-icon" name="pause-fill" library="system"></sl-icon>`
: html`<sl-icon part="play-icon" name="play-fill" library="system"></sl-icon>`}
</div>
`
: ''}
</div>
`;
}
}
declare global {
interface HTMLElementTagNameMap {
'sl-animated-image': SlAnimatedImage;
}
}

Wyświetl plik

@ -51,6 +51,16 @@ const icons = {
<path d="M3 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H3zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/>
</svg>
`,
'play-fill': `
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-play-fill" viewBox="0 0 16 16">
<path d="m11.596 8.697-6.363 3.692c-.54.313-1.233-.066-1.233-.697V4.308c0-.63.692-1.01 1.233-.696l6.363 3.692a.802.802 0 0 1 0 1.393z"></path>
</svg>
`,
'pause-fill': `
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-pause-fill" viewBox="0 0 16 16">
<path d="M5.5 3.5A1.5 1.5 0 0 1 7 5v6a1.5 1.5 0 0 1-3 0V5a1.5 1.5 0 0 1 1.5-1.5zm5 0A1.5 1.5 0 0 1 12 5v6a1.5 1.5 0 0 1-3 0V5a1.5 1.5 0 0 1 1.5-1.5z"></path>
</svg>
`,
'star-fill': `
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-star-fill" viewBox="0 0 16 16">
<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>

Wyświetl plik

@ -1,5 +1,6 @@
// Components
export { default as SlAlert } from './components/alert/alert';
export { default as SlAnimatedImage } from './components/animated-image/animated-image';
export { default as SlAnimation } from './components/animation/animation';
export { default as SlAvatar } from './components/avatar/avatar';
export { default as SlBadge } from './components/badge/badge';