finished documentation
This commit is contained in:
parent
371b81e1f5
commit
495ac8080f
3 changed files with 203 additions and 140 deletions
|
|
@ -6,40 +6,75 @@ import * as VideoStories from './Video.stories.svelte';
|
|||
|
||||
# Video
|
||||
|
||||
The `Video` component adds a video with various controls to your page. The video can play on load or when the video comes into view. The component supports videos with or without audio, and has options to add a pause/play button or to allow the user to click on the video to pause and play it instead.
|
||||
The `Video` component adds a video with various controls to your page such as:
|
||||
|
||||
- Play/pause button
|
||||
- Autoplay controls, i.e. whether the video plays when it comes into view or on page load
|
||||
- Looping
|
||||
- Audio controls
|
||||
- Text elements such as notes and titles
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { Video } from '@reuters-graphics/graphics-components';
|
||||
import { assets } from '$app/paths'; // If using local video in the Graphics Kit
|
||||
</script>
|
||||
|
||||
<Video
|
||||
ariaDescription={'Compulsory description of your video for screen readers.'}
|
||||
src={`${assets}/videos/myVideo.mp4`}
|
||||
width={'wide'}
|
||||
caption={'Optional caption for your video.'}
|
||||
ariaDescription="Required description of your video for screen readers."
|
||||
src="my-video.mp4"
|
||||
width="wide"
|
||||
notes="Optional caption for your video."
|
||||
/>
|
||||
```
|
||||
|
||||
<Canvas of={VideoStories.Demo} />
|
||||
|
||||
## Playing and looping
|
||||
## Using with ArchieML docs
|
||||
|
||||
`playVideoWhenInView`, `playVideoThreshold`
|
||||
With the Graphics Kit, you'll likely get your text value from an ArchieML doc...
|
||||
|
||||
- By default, the video will **start playing when 50% of the video element's height is visible on the page**.
|
||||
To control the threshold of visibility at which the video starts playing, add the prop `playVideoThreshold` and set it to a value between 0 and 1,
|
||||
where 0 means that the video will start playing as soon as its top enters the viewport, while 1 means it will start when the whole video is in the viewport.
|
||||
```yaml
|
||||
# ArchieML doc
|
||||
[blocks]
|
||||
type: video
|
||||
src: videos/my-video.mp4
|
||||
width: wide
|
||||
ariaDescription: Required description of your video for screen readers.
|
||||
notes: Optional caption for your video.
|
||||
loopVideo: true
|
||||
[]
|
||||
```
|
||||
|
||||
- If you don't want the video to play when you scroll to it, but **on page load**, add the prop `playVideoWhenInView={false}`. The default of the prop is `true`,
|
||||
which corresponds to the behaviour described above.
|
||||
... which you'll parse out of a ArchieML block object before passing to the `Video` component.
|
||||
|
||||
`loopVideo`
|
||||
```svelte
|
||||
<!-- App.svelte -->
|
||||
<script>
|
||||
import { Video } from '@reuters-graphics/graphics-components';
|
||||
import { assets } from '$app/paths'; // 👈 If using in the Graphics Kit...
|
||||
import { truthy } from '$utils/propValidators'; // 👈 If using in the Graphics Kit...
|
||||
</script>
|
||||
|
||||
- By default, the video will **loop**. If you don't want that, add the prop `loopVideo={false}`.
|
||||
{#each content.blocks as block}
|
||||
{#if block.type === 'video'}
|
||||
<Video
|
||||
ariaDescription={block.ariaDescription}
|
||||
src={`${assets}/${block.src}`}
|
||||
width={block.width}
|
||||
loopVideo={truthy(block.loopVideo)}
|
||||
notes={block.notes}
|
||||
/>
|
||||
{/if}
|
||||
{/each}
|
||||
```
|
||||
|
||||
Here is an example of what the same video would look like with a visibility threshold of 0.9 and not looping. Scroll down slowly to observe the behaviour.
|
||||
> **Note:** Some props, like `loopVideo`, expect boolean values. If you're using the Graphics Kit, you can use the `truthy()` util function to convert a string value to a boolean.
|
||||
|
||||
## Autoplay controls
|
||||
|
||||
By default, the video starts playing when 50% (0.5) of the video element's height comes into view. Adjust this with `playVideoThreshold`, which is a value between 0 and 1, where 0 means the video will start playing as soon as its top enters the viewport, and 1 means it will start when the whole video is in the viewport.
|
||||
|
||||
To make the video play on page load regardless of whether it is in view, set the prop `playVideoWhenInView` to `false`.
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
|
|
@ -47,38 +82,23 @@ Here is an example of what the same video would look like with a visibility thre
|
|||
</script>
|
||||
|
||||
<Video
|
||||
ariaDescription={'Compulsory description of your video for screen readers.'}
|
||||
src={'path-to-video-or-external-url'}
|
||||
width={'normal'}
|
||||
loopVideo={false}
|
||||
ariaDescription="Required description of your video for screen readers."
|
||||
src="https://..."
|
||||
loopVideo={true}
|
||||
playVideoThreshold={0.9}
|
||||
notes="World's longest glass bridge opens to public in Vietnam. (c) 2022 Thomson Reuters"
|
||||
/>
|
||||
```
|
||||
|
||||
<Canvas of={VideoStories.PlayingAndLooping} />
|
||||
<Canvas of={VideoStories.Autoplay} />
|
||||
|
||||
## Audio controls
|
||||
|
||||
If you've ever had to put sound on a page in recent years,
|
||||
you'll know that auto-playing sound is not allowed by browsers. The user will need to interact with the page first, and it will depend on your
|
||||
particular use case how and when you'd like this to happen. This component provides two options to deal with this.
|
||||
If you have a video with sound, make sure to add the prop `muteVideo={false}`.
|
||||
On most browsers, [autoplaying videos with sound](https://developer.chrome.com/blog/autoplay#:~:text=Muted%20autoplay%20is%20always%20allowed,to%20allow%20autoplay%20with%20sound.) is allowed only if the user has interacted with the page. (Autoplay is allowed with muted videos.)
|
||||
|
||||
Then you can either:
|
||||
By default, this component will not autoplay videos with sound. To change this, set `soundAutoplay` to `true`. This will allow the video to autoplay with sound when it comes into view, but only if the user has already interacted with the page by clicking or tapping on it.
|
||||
|
||||
- `allowSoundToAutoplay={false}` (default) : Don't allow the video to autoplay under any circumstances other than when the user clicks the 'play' on the video. Note that this
|
||||
works whether or not you have the controls visible, i.e. with `showControls` being `true` or `false`, as long as you allow
|
||||
play/pause behaviour with `possibleToPlayPause={true}` (default).
|
||||
|
||||
- `allowSoundToAutoplay={true}` : Allow the video to autoplay when it comes into view as long as the user has interacted with the page preivously, i.e. they have clicked/tapped
|
||||
anywhere on the page.
|
||||
|
||||
You should keep `playVideoWhenInView={true}` (default). There is no option to autoplay video with sound when the user clicks on the page
|
||||
elsewhere if the video is not in view. In other words, you can't start playing sound for a video which is not in view with this component.
|
||||
This is probably not a behaviour you'd want anyway.
|
||||
|
||||
The example below allows for autoplay if the user has interacted with the page before the video comes into view. To see this, reload the page
|
||||
and go to the top. Click anywhere on the page before scrolling down to the video and you should see it autoplay when it comes into view.
|
||||
Test this with the example below: the video will autoplay when it comes into view _only if_ you have clicked or tapped on the page before scrolling down to it.
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
|
|
@ -86,14 +106,50 @@ and go to the top. Click anywhere on the page before scrolling down to the video
|
|||
</script>
|
||||
|
||||
<Video
|
||||
ariaDescription={'Compulsory description of your video for screen readers.'}
|
||||
src={'path-to-video-or-external-url'}
|
||||
width={'normal'}
|
||||
controlsOpacity={1}
|
||||
loopVideo={false}
|
||||
ariaDescription="Required description of your video for screen readers."
|
||||
src="https://..."
|
||||
controlsColour="#152a1c"
|
||||
controlsOpacityMax={1}
|
||||
controlsOpacityMin={0.5}
|
||||
muteVideo={false}
|
||||
allowSoundToAutoplay={true}
|
||||
soundAutoplay={true}
|
||||
/>
|
||||
```
|
||||
|
||||
<Canvas of={VideoStories.Audio} />
|
||||
|
||||
## Adding text
|
||||
|
||||
The `Video` component allows you to add a title, description and notes to your video, which are rendered by the `GraphicBlock` component.
|
||||
|
||||
Customise the `notes` section by passing a [snippet](https://svelte.dev/docs/svelte/snippet) instead of a string.
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { Video } from '@reuters-graphics/graphics-components';
|
||||
</script>
|
||||
|
||||
<Video
|
||||
src="https://..."
|
||||
ariaDescription="Required description of your video for screen readers."
|
||||
title="Title for your video"
|
||||
description="Description for your video"
|
||||
>
|
||||
<!-- Custom notes snippet -->
|
||||
{#snippet notes()}
|
||||
<aside>
|
||||
<p class="notes">Custom-styled notes for the video.</p>
|
||||
</aside>
|
||||
{/snippet}
|
||||
</Video>
|
||||
|
||||
<style lang="scss">
|
||||
@use '@reuters-graphics/graphics-components/dist/scss/mixins' as mixins;
|
||||
|
||||
p.notes {
|
||||
@include mixins.body-note;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
<Canvas of={VideoStories.Text} />
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
<Story
|
||||
name="Demo"
|
||||
args={{
|
||||
ariaDescription: 'Compulsory description of your video for screen readers.',
|
||||
ariaDescription: 'Required description of your video for screen readers.',
|
||||
src: SilentVideo,
|
||||
width: 'wide',
|
||||
notes: 'Optional caption for your video.',
|
||||
|
|
@ -24,12 +24,11 @@
|
|||
/>
|
||||
|
||||
<Story
|
||||
name="Playing and looping"
|
||||
exportName="PlayingAndLooping"
|
||||
name="Autoplay controls"
|
||||
exportName="Autoplay"
|
||||
args={{
|
||||
ariaDescription: 'Compulsory description of your video for screen readers.',
|
||||
ariaDescription: 'Required description of your video for screen readers.',
|
||||
src: SilentVideo,
|
||||
width: 'normal',
|
||||
loopVideo: true,
|
||||
notes:
|
||||
"World's longest glass bridge opens to public in Vietnam. (c) 2022 Thomson Reuters",
|
||||
|
|
@ -37,37 +36,61 @@
|
|||
}}
|
||||
/>
|
||||
|
||||
<Story
|
||||
name="Audio controls"
|
||||
exportName="Audio"
|
||||
args={{
|
||||
ariaDescription: 'Required description of your video for screen readers.',
|
||||
src: SoundVideo,
|
||||
notes:
|
||||
"World's longest glass bridge opens to public in Vietnam. (c) 2022 Thomson Reuters",
|
||||
controlsColour: '#152a1c',
|
||||
controlsOpacityMax: 1,
|
||||
controlsOpacityMin: 0.5,
|
||||
playVideoThreshold: 0.9,
|
||||
muteVideo: false,
|
||||
soundAutoplay: true,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Story
|
||||
name="Controls"
|
||||
args={{
|
||||
ariaDescription: 'Compulsory description of your video for screen readers.',
|
||||
ariaDescription: 'Required description of your video for screen readers.',
|
||||
src: SilentVideo,
|
||||
width: 'normal',
|
||||
notes:
|
||||
"World's longest glass bridge opens to public in Vietnam. (c) 2022 Thomson Reuters",
|
||||
playVideoThreshold: 0.9,
|
||||
controlsColour: 'white',
|
||||
controlsOpacity: 1,
|
||||
controlsOpacityMax: 1,
|
||||
controlsOpacityMin: 0.5,
|
||||
controlsPosition: 'bottom right',
|
||||
separateReplayIcon: true,
|
||||
loopVideo: false,
|
||||
soundAutoplay: true,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Story
|
||||
name="Audio controls"
|
||||
exportName="Audio"
|
||||
args={{
|
||||
ariaDescription: 'Compulsory description of your video for screen readers.',
|
||||
src: SoundVideo,
|
||||
width: 'normal',
|
||||
notes:
|
||||
"World's longest glass bridge opens to public in Vietnam. (c) 2022 Thomson Reuters",
|
||||
playVideoThreshold: 0.9,
|
||||
showControls: true,
|
||||
loopVideo: false,
|
||||
muteVideo: false,
|
||||
playVideoWhenInView: true,
|
||||
allowSoundToAutoplay: true,
|
||||
}}
|
||||
/>
|
||||
<Story name="Text elements" exportName="Text">
|
||||
<Video
|
||||
src={SilentVideo}
|
||||
ariaDescription="Required description of your video for screen readers."
|
||||
title="Title for your video"
|
||||
description="Description for your video"
|
||||
>
|
||||
{#snippet notes()}
|
||||
<aside>
|
||||
<p class="notes">Custom-styled notes for the video.</p>
|
||||
</aside>
|
||||
{/snippet}
|
||||
</Video>
|
||||
</Story>
|
||||
|
||||
<style lang="scss">
|
||||
@use '../../scss/mixins' as mixins;
|
||||
|
||||
p.notes {
|
||||
@include mixins.body-note;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -18,16 +18,14 @@
|
|||
src: string;
|
||||
/** Image to be shown while the video is downloading */
|
||||
poster?: string;
|
||||
/** Whether to wrap the graphic with an aria hidden tag. */
|
||||
hidden?: boolean;
|
||||
/** ARIA description, passed in as a markdown string. */
|
||||
ariaDescription?: string;
|
||||
ariaDescription: string;
|
||||
/** Add extra classes to the block tag to target it with custom CSS. */
|
||||
class?: string;
|
||||
/** Title of the graphic */
|
||||
title?: string;
|
||||
/** Notes to the graphic, passed in as a markdown string OR a custom snippet. */
|
||||
notes: string | Snippet;
|
||||
notes?: string | Snippet;
|
||||
/** Description of the graphic, passed in as a markdown string. */
|
||||
description?: string;
|
||||
/** Width of the block within the article well. */
|
||||
|
|
@ -41,10 +39,10 @@
|
|||
/** Whether video should have sound or not. */
|
||||
muteVideo?: boolean;
|
||||
/** If `true`, this allow videos with sound to autoplay if the user has previously interacted with DOM */
|
||||
sountAutoplay?: boolean;
|
||||
soundAutoplay?: boolean;
|
||||
/** Whether the video should play when it comes into view or just on page load */
|
||||
playVideoWhenInView?: boolean;
|
||||
/** If video plays with intersection observer, how much of it should be into view to start playing */
|
||||
/** Controls how much of the video should be visible when it starts playing. This is a number between 0 and 1, where 0 means the video will start playing as soon as its top enters the viewport, and 1 means it will start when the whole video is in the viewport. */
|
||||
playVideoThreshold?: number;
|
||||
/** Whether to have the option to pause and play video */
|
||||
possibleToPlayPause?: boolean;
|
||||
|
|
@ -67,7 +65,6 @@
|
|||
let {
|
||||
src,
|
||||
poster = '',
|
||||
hidden = true,
|
||||
ariaDescription,
|
||||
class: cls = '',
|
||||
title,
|
||||
|
|
@ -78,7 +75,7 @@
|
|||
preloadVideo = 'auto',
|
||||
loopVideo = false,
|
||||
muteVideo = true,
|
||||
sountAutoplay = false,
|
||||
soundAutoplay = false,
|
||||
playVideoWhenInView = true,
|
||||
playVideoThreshold = 0.5,
|
||||
possibleToPlayPause = true,
|
||||
|
|
@ -138,7 +135,7 @@
|
|||
// Special case for video with sound
|
||||
// Only ff you've clicked on play button or interacted with DOM in any way previously, video with audio will play
|
||||
if (
|
||||
sountAutoplay &&
|
||||
soundAutoplay &&
|
||||
playVideoWhenInView &&
|
||||
intersecting &&
|
||||
!muteVideo &&
|
||||
|
|
@ -147,19 +144,13 @@
|
|||
)
|
||||
paused = false;
|
||||
|
||||
if (sountAutoplay && !muteVideo && !interactedWithDom) paused = true;
|
||||
if (soundAutoplay && !muteVideo && !interactedWithDom) paused = true;
|
||||
});
|
||||
|
||||
// Warning to missing aria attributes
|
||||
if (hidden && !ariaDescription) {
|
||||
console.warn(
|
||||
'Must provide aria description for video components if hidden is true.'
|
||||
);
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Controls button snippet -->
|
||||
{#snippet controls()}
|
||||
clickedOnPauseBtn:{clickedOnPauseBtn}
|
||||
<button
|
||||
class="controls"
|
||||
onclick={() => {
|
||||
|
|
@ -233,58 +224,17 @@
|
|||
interactiveControlsOpacity = controlsOpacityMin;
|
||||
}}
|
||||
>
|
||||
{#if (hidden && ariaDescription) || !hidden}
|
||||
{#if ariaDescription}
|
||||
<p class="visually-hidden">{ariaDescription}</p>
|
||||
{/if}
|
||||
|
||||
{#if playVideoWhenInView}
|
||||
<!-- Video element with Intersection Observer -->
|
||||
<IntersectionObserver
|
||||
{element}
|
||||
bind:intersecting
|
||||
threshold={playVideoThreshold}
|
||||
once={false}
|
||||
>
|
||||
<div
|
||||
bind:this={element}
|
||||
class="video-wrapper relative block"
|
||||
aria-hidden={hidden}
|
||||
bind:clientWidth={videoWidthContainer}
|
||||
bind:clientHeight={videoHeightContainer}
|
||||
>
|
||||
{#if possibleToPlayPause}
|
||||
{#if showControls}
|
||||
{@render controls()}
|
||||
{:else}
|
||||
{@render transparentButton()}
|
||||
{/if}
|
||||
{/if}
|
||||
<video
|
||||
bind:this={videoElement}
|
||||
{src}
|
||||
{poster}
|
||||
class="pointer-events-none relative"
|
||||
width="100%"
|
||||
muted={muteVideo}
|
||||
playsinline
|
||||
preload={preloadVideo}
|
||||
loop={loopVideo}
|
||||
bind:currentTime={time}
|
||||
bind:duration
|
||||
bind:paused
|
||||
bind:clientWidth={videoWidth}
|
||||
bind:clientHeight={videoHeight}
|
||||
>
|
||||
<track kind="captions" />
|
||||
</video>
|
||||
</div>
|
||||
</IntersectionObserver>
|
||||
{:else}
|
||||
<!-- Video element without Intersection observer -->
|
||||
{#if playVideoWhenInView}
|
||||
<!-- Video element with Intersection Observer -->
|
||||
<IntersectionObserver
|
||||
{element}
|
||||
bind:intersecting
|
||||
threshold={playVideoThreshold}
|
||||
once={false}
|
||||
>
|
||||
<div
|
||||
class="video-wrapper relative"
|
||||
aria-hidden={hidden}
|
||||
bind:this={element}
|
||||
class="video-wrapper relative block"
|
||||
bind:clientWidth={videoWidthContainer}
|
||||
bind:clientHeight={videoHeightContainer}
|
||||
>
|
||||
|
|
@ -305,21 +255,55 @@
|
|||
playsinline
|
||||
preload={preloadVideo}
|
||||
loop={loopVideo}
|
||||
aria-label={ariaDescription}
|
||||
bind:currentTime={time}
|
||||
bind:duration
|
||||
bind:paused
|
||||
autoplay
|
||||
bind:clientWidth={videoWidth}
|
||||
bind:clientHeight={videoHeight}
|
||||
>
|
||||
<track kind="captions" />
|
||||
</video>
|
||||
</div>
|
||||
{/if}
|
||||
</IntersectionObserver>
|
||||
{:else}
|
||||
<!-- Video element without Intersection observer -->
|
||||
<div
|
||||
class="video-wrapper relative"
|
||||
bind:clientWidth={videoWidthContainer}
|
||||
bind:clientHeight={videoHeightContainer}
|
||||
>
|
||||
{#if possibleToPlayPause}
|
||||
{#if showControls}
|
||||
{@render controls()}
|
||||
{:else}
|
||||
{@render transparentButton()}
|
||||
{/if}
|
||||
{/if}
|
||||
<video
|
||||
bind:this={videoElement}
|
||||
{src}
|
||||
{poster}
|
||||
class="pointer-events-none relative"
|
||||
width="100%"
|
||||
muted={muteVideo}
|
||||
playsinline
|
||||
preload={preloadVideo}
|
||||
loop={loopVideo}
|
||||
bind:currentTime={time}
|
||||
bind:duration
|
||||
bind:paused
|
||||
autoplay
|
||||
bind:clientWidth={videoWidth}
|
||||
bind:clientHeight={videoHeight}
|
||||
>
|
||||
<track kind="captions" />
|
||||
</video>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<!-- Custom notes snippet -->
|
||||
{#if notes && typeof notes !== 'string'}
|
||||
<!-- Custom notes and source slot -->
|
||||
{@render notes()}
|
||||
{/if}
|
||||
</GraphicBlock>
|
||||
|
|
|
|||
Loading…
Reference in a new issue