kopia lustrzana https://github.com/shoelace-style/shoelace
add animated-image
rodzic
ff84beaade
commit
be662ddf32
|
@ -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 |
|
@ -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]
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
`;
|
|
@ -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;
|
||||
});
|
||||
});
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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"/>
|
||||
|
|
|
@ -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';
|
||||
|
|
Ładowanie…
Reference in New Issue