Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(youtube): thumbnailSize prop with fallback support #376

Merged
merged 9 commits into from
Feb 7, 2025
3 changes: 0 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -17,9 +17,6 @@ jobs:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
version: 9.15.2
run_install: false
- name: Install Node.js
uses: actions/setup-node@v4
with:
4 changes: 0 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -16,10 +16,6 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
version: 9.15.2
run_install: false
- name: Install Node.js
uses: actions/setup-node@v4
with:
18 changes: 16 additions & 2 deletions docs/content/scripts/content/youtube-player.md
Original file line number Diff line number Diff line change
@@ -105,9 +105,23 @@ To modify this behavior, you can set the `host` prop to `https://www.youtube.com
<ScriptYouTubePlayer video-id="d_IFKP1Ofq0" :player-options="{ host: 'https://www.youtube.com' }" />
```

#### Eager Loading Placeholder
### Placeholder

The YouTube Player placeholder image is lazy-loaded by default. You should change this behavior if your video is above the fold
The YouTube Player placeholder image is 1280x720 webp that is lazy-loaded by default.

To modify the placeholder size you can set the `thumbnailSize` prop, if you'd prefer
to use a `jpg` you can pass the `webp` prop as `false`.

```vue
<ScriptYouTubePlayer video-id="d_IFKP1Ofq0" thumbnail-size="maxresdefault" />
```

If you need fine control over the placeholder you can set `placeholderAttrs` prop or completely override it using
the `#placeholder` slot.

#### Eager Loading

You should change this behavior if your video is above the fold
or consider using the `#placeholder` slot to customize the placeholder image.

::code-group
13 changes: 12 additions & 1 deletion playground/pages/third-parties/youtube/nuxt-scripts.vue
Original file line number Diff line number Diff line change
@@ -6,6 +6,10 @@ const videoid = ref('d_IFKP1Ofq0')
function changeVideo() {
videoid.value = 'N8siuNjyV7A'
}
function changeVideoFallback() {
videoid.value = 'jNQXAC9IVRw'
}
</script>

<template>
@@ -14,7 +18,7 @@ function changeVideo() {
Multiple YouTube Players
</NuxtLink>
<div>
<ScriptYouTubePlayer :video-id="videoid" above-the-fold>
<ScriptYouTubePlayer :video-id="videoid" above-the-fold class="w-[640px] h-[360px]">
<template #awaitingLoad>
<div class="text-lg top-5 absolute left-10">
Nuxt Nation 2023: Daniel Roe - A New Nuxt - Release of Nuxt v3.8
@@ -31,5 +35,12 @@ function changeVideo() {
>
change video
</UButton>

<UButton
class="ml-5"
@click="changeVideoFallback"
>
change video needs fallback
</UButton>
</div>
</template>
432 changes: 159 additions & 273 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

34 changes: 29 additions & 5 deletions src/runtime/components/ScriptYouTubePlayer.vue
Original file line number Diff line number Diff line change
@@ -9,6 +9,20 @@ import { useScriptTriggerElement } from '../composables/useScriptTriggerElement'
import { useScriptYouTubePlayer } from '../registry/youtube-player'
import ScriptAriaLoadingIndicator from './ScriptAriaLoadingIndicator.vue'
export type YoutubeThumbnailSize =
// 120x90
'1' | '2' | '3' | 'default' |
// 320x180
'mq1' | 'mq2' | 'mq3' | 'mqdefault' |
// 480x360
'0' | 'hq1' | 'hq2' | 'hq3' | 'hqdefault' |
// 640x480
'sd1' | 'sd2' | 'sd3' | 'sddefault' |
// 1280x720
'hq720' |
// 1920x1080
'maxresdefault'
const props = withDefaults(defineProps<{
placeholderAttrs?: ImgHTMLAttributes
rootAttrs?: HTMLAttributes
@@ -25,9 +39,13 @@ const props = withDefaults(defineProps<{
*/
cookies?: boolean
playerOptions?: YT.PlayerOptions
thumbnailSize?: YoutubeThumbnailSize
webp?: boolean
}>(), {
cookies: false,
trigger: 'mousedown',
thumbnailSize: 'hq720',
webp: true,
// @ts-expect-error untyped
playerVars: { autoplay: 0, playsinline: 1 },
width: 640,
@@ -126,15 +144,15 @@ const rootAttrs = computed(() => {
position: 'relative',
backgroundColor: 'black',
maxWidth: '100%',
width: `auto`,
height: 'auto',
aspectRatio: `${props.width}/${props.height}`,
},
...(trigger instanceof Promise ? trigger.ssrAttrs || {} : {}),
}) as HTMLAttributes
})
const placeholder = computed(() => `https://i.ytimg.com/vi_webp/${props.videoId}/sddefault.webp`)
const fallbackPlaceHolder = computed(() => `https://i.ytimg.com/vi/${props.videoId}/hqdefault.jpg`)
const placeholder = computed(() => `https://i.ytimg.com/${props.webp ? 'vi_webp' : 'vi'}/${props.videoId}/${props.thumbnailSize}.${props.webp ? 'webp' : 'jpg'}`)
const isFallbackPlaceHolder = ref(false)
if (import.meta.server) {
// dns-prefetch https://i.vimeocdn.com
@@ -160,14 +178,20 @@ if (import.meta.server) {
const placeholderAttrs = computed(() => {
return defu(props.placeholderAttrs, {
src: placeholder.value,
src: isFallbackPlaceHolder.value ? fallbackPlaceHolder.value : placeholder.value,
alt: '',
loading: props.aboveTheFold ? 'eager' : 'lazy',
style: {
width: '100%',
objectFit: 'cover',
objectFit: 'contain',
height: '100%',
},
onLoad(payload) {
const img = payload.target as HTMLImageElement
if (img.naturalWidth === 120 && img.naturalHeight === 90) {
isFallbackPlaceHolder.value = true
}
},
} satisfies ImgHTMLAttributes)
})
</script>