Merge pull request #429 from taroj1205/fix/video

Implement lazy loading of videos
This commit is contained in:
mr. m 2025-01-27 15:33:06 +01:00 committed by GitHub
commit d32077cf6d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 359 additions and 250 deletions

View file

@ -18,5 +18,5 @@ export default defineConfig({
site: 'https://zen-browser.app', site: 'https://zen-browser.app',
redirects: { redirects: {
'/themes/[...slug]': '/mods/[...slug]', '/themes/[...slug]': '/mods/[...slug]',
}, }
}) })

View file

@ -1,7 +1,5 @@
--- ---
import Title from '../components/Title.astro'
import Description from '../components/Description.astro' import Description from '../components/Description.astro'
import { Image } from 'astro:assets'
import browserSidebar from '../assets/browser-sidebar.webm' import browserSidebar from '../assets/browser-sidebar.webm'
import browserWorkspaces from '../assets/browser-workspaces.webm' import browserWorkspaces from '../assets/browser-workspaces.webm'
@ -10,7 +8,8 @@ import browserGlance from '../assets/browser-glance.webm'
import browserSplitViews from '../assets/browser-splitview.webm' import browserSplitViews from '../assets/browser-splitview.webm'
import { motion } from 'motion/react' import { motion } from 'motion/react'
import { getTitleAnimation, getZoomInAnimation } from '../animations' import { getTitleAnimation } from '../animations'
import Video from './Video.astro'
--- ---
<section <section
@ -47,14 +46,15 @@ import { getTitleAnimation, getZoomInAnimation } from '../animations'
organized. organized.
</p> </p>
</div> </div>
<video <Video
src={browserWorkspaces} src={browserWorkspaces}
autoplay autoplay
loop loop
muted muted
playsinline playsinline
preload="none" preload="none"
class="rounded-xl border-4 border-white object-cover shadow"></video> class="rounded-xl border-4 border-white object-cover shadow"
/>
</div> </div>
<div class="long-feature xl:!flex-row-reverse"> <div class="long-feature xl:!flex-row-reverse">
<div class="lg:p-24"> <div class="lg:p-24">
@ -66,14 +66,15 @@ import { getTitleAnimation, getZoomInAnimation } from '../animations'
distractions. distractions.
</p> </p>
</div> </div>
<video <Video
src={browserCompactMode} src={browserCompactMode}
autoplay autoplay
loop loop
muted muted
playsinline playsinline
preload="none" preload="none"
class="rounded-xl border-4 border-white object-cover shadow"></video> class="rounded-xl border-4 border-white object-cover shadow"
/>
</div> </div>
<div class="long-feature"> <div class="long-feature">
<div class="lg:p-24"> <div class="lg:p-24">
@ -87,14 +88,15 @@ import { getTitleAnimation, getZoomInAnimation } from '../animations'
opening them. opening them.
</p> </p>
</div> </div>
<video <Video
src={browserGlance} src={browserGlance}
autoplay autoplay
loop loop
muted muted
playsinline playsinline
preload="none" preload="none"
class="rounded-xl border-4 border-white object-cover shadow"></video> class="rounded-xl border-4 border-white object-cover shadow"
/>
</div> </div>
<div class="long-feature xl:!flex-row-reverse"> <div class="long-feature xl:!flex-row-reverse">
<div class="lg:p-24"> <div class="lg:p-24">
@ -107,14 +109,15 @@ import { getTitleAnimation, getZoomInAnimation } from '../animations'
Zen's split view feature allows you to view multiple tabs at once. Zen's split view feature allows you to view multiple tabs at once.
</p> </p>
</div> </div>
<video <Video
src={browserSplitViews} src={browserSplitViews}
autoplay autoplay
loop loop
muted muted
playsinline playsinline
preload="none" preload="none"
class="rounded-xl border-4 border-white object-cover shadow"></video> class="rounded-xl border-4 border-white object-cover shadow"
/>
</div> </div>
<div class="long-feature"> <div class="long-feature">
<div class="lg:p-24"> <div class="lg:p-24">
@ -125,14 +128,15 @@ import { getTitleAnimation, getZoomInAnimation } from '../animations'
Zen's sidebar feature allows you to view all your tabs in one place. Zen's sidebar feature allows you to view all your tabs in one place.
</p> </p>
</div> </div>
<video <Video
src={browserSidebar} src={browserSidebar}
autoplay autoplay
loop loop
muted muted
playsinline playsinline
preload="none" preload="none"
class="rounded-xl border-4 border-white object-cover shadow"></video> class="rounded-xl border-4 border-white object-cover shadow"
/>
</div> </div>
</div> </div>
</section> </section>

View file

@ -12,7 +12,7 @@ import {
} from 'astro-navbar' } from 'astro-navbar'
import { ArrowRight, ChevronDown, Download, DownloadCloud } from 'lucide-astro' import { ArrowRight, ChevronDown, Download, DownloadCloud } from 'lucide-astro'
import Logo from './Logo.astro' import Logo from './Logo.astro'
import { ThemeSwitch } from 'free-astro-components' import ThemeSwitch from './ThemeSwitch.astro'
--- ---
<nav <nav
@ -112,7 +112,7 @@ import { ThemeSwitch } from 'free-astro-components'
</div> </div>
<div class="ml-auto flex gap-2"> <div class="ml-auto flex gap-2">
<div id="theme-switcher" class="ml-auto md:mr-2"> <div id="theme-switcher" class="ml-auto md:mr-2">
<ThemeSwitch label="" class="px-1 py-2" /> <ThemeSwitch />
</div> </div>
<Button href="/download" class="ml-auto" isPrimary> <Button href="/download" class="ml-auto" isPrimary>
<span class="hidden items-center gap-2 md:flex"> <span class="hidden items-center gap-2 md:flex">

View file

@ -0,0 +1,33 @@
---
interface Props {
label?: string
class?: string
}
const { label = 'Toggle theme', class: className, ...rest } = Astro.props
import SunIcon from '../icons/SunIcon.astro'
import MoonIcon from '../icons/MoonIcon.astro'
---
<button
id="theme-toggle"
type="button"
class:list={['rounded-lg p-2.5 outline-none', className]}
aria-label={label}
{...rest}
>
<SunIcon class="hidden size-5 dark:block" />
<MoonIcon class="size-5 dark:hidden" />
</button>
<script>
document.addEventListener('astro:page-load', () => {
const themeToggleBtn = document.getElementById('theme-toggle')
themeToggleBtn?.addEventListener('click', () => {
const currentTheme = document.documentElement.dataset.theme
const newTheme = currentTheme === 'dark' ? 'light' : 'dark'
localStorage.theme = newTheme
document.documentElement.dataset.theme = newTheme
})
})
</script>

View file

@ -0,0 +1,42 @@
---
const { src, class: className, ...rest } = Astro.props
const type = src.split('.').pop() || 'webm'
---
<video
class:list={['w-full', className]}
data-src={src}
preload="none"
{...rest}
>
<source src="" type={`video/${type}`} />
</video>
<script>
document.addEventListener('astro:page-load', () => {
const videos = document.querySelectorAll('video[data-src]')
const loadVideo = (video: HTMLVideoElement) => {
const source = video.querySelector('source')
const dataSrc = video.getAttribute('data-src')
if (dataSrc && source) {
source.src = dataSrc
video.load()
video.removeAttribute('data-src')
}
}
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
loadVideo(entry.target as HTMLVideoElement)
observer.unobserve(entry.target)
}
})
})
videos.forEach((video) => {
observer.observe(video)
})
})
</script>

9
src/icons/MoonIcon.astro Normal file
View file

@ -0,0 +1,9 @@
---
const { class: className, ...rest } = Astro.props
---
<svg class={className} viewBox="0 0 24 24" {...rest}>
<path
d="M10.719 2.082c-2.572 2.028-4.719 5.212-4.719 9.918 0 4.569 1.938 7.798 4.548 9.895-4.829-.705-8.548-4.874-8.548-9.895 0-5.08 3.808-9.288 8.719-9.918zm1.281-2.082c-6.617 0-12 5.383-12 12s5.383 12 12 12c1.894 0 3.87-.333 5.37-1.179-3.453-.613-9.37-3.367-9.37-10.821 0-7.555 6.422-10.317 9.37-10.821-1.74-.682-3.476-1.179-5.37-1.179zm0 10.999c1.437.438 2.562 1.564 2.999 3.001.44-1.437 1.565-2.562 3.001-3-1.436-.439-2.561-1.563-3.001-3-.437 1.436-1.562 2.561-2.999 2.999zm8.001.001c.958.293 1.707 1.042 2 2.001.291-.959 1.042-1.709 1.999-2.001-.957-.292-1.707-1.042-2-2-.293.958-1.042 1.708-1.999 2zm-1-9c-.437 1.437-1.563 2.562-2.998 3.001 1.438.44 2.561 1.564 3.001 3.002.437-1.438 1.563-2.563 2.996-3.002-1.433-.437-2.559-1.564-2.999-3.001z"
fill="currentColor"></path>
</svg>

9
src/icons/SunIcon.astro Normal file
View file

@ -0,0 +1,9 @@
---
const { class: className, ...rest } = Astro.props
---
<svg class={className} viewBox="0 0 24 24" {...rest}>
<path
d="M12 9c1.654 0 3 1.346 3 3s-1.346 3-3 3-3-1.346-3-3 1.346-3 3-3zm0-2c-2.762 0-5 2.238-5 5s2.238 5 5 5 5-2.238 5-5-2.238-5-5-5zm-4.184-.599l-3.593-3.594-1.415 1.414 3.595 3.595c.401-.537.876-1.013 1.413-1.415zm4.184-1.401c.34 0 .672.033 1 .08v-5.08h-2v5.08c.328-.047.66-.08 1-.08zm5.598 2.815l3.595-3.595-1.414-1.414-3.595 3.595c.537.402 1.012.878 1.414 1.414zm-12.598 4.185c0-.34.033-.672.08-1h-5.08v2h5.08c-.047-.328-.08-.66-.08-1zm11.185 5.598l3.594 3.593 1.415-1.414-3.594-3.593c-.403.536-.879 1.012-1.415 1.414zm-9.784-1.414l-3.593 3.593 1.414 1.414 3.593-3.593c-.536-.402-1.011-.877-1.414-1.414zm12.519-5.184c.047.328.08.66.08 1s-.033.672-.08 1h5.08v-2h-5.08zm-6.92 8c-.34 0-.672-.033-1-.08v5.08h2v-5.08c-.328.047-.66.08-1 .08z"
fill="currentColor"></path>
</svg>

View file

@ -7,29 +7,32 @@ const { title } = Astro.props
import '@fontsource/bricolage-grotesque/400.css' import '@fontsource/bricolage-grotesque/400.css'
import NavBar from '../components/NavBar.astro' import NavBar from '../components/NavBar.astro'
import Footer from '../components/Footer.astro' import Footer from '../components/Footer.astro'
import { ClientRouter } from 'astro:transitions'
--- ---
<script is:inline> <script is:inline>
const theme = (() => { function setDarkMode(document) {
if (typeof localStorage !== 'undefined' && localStorage.getItem('theme')) { let theme = localStorage.theme
return localStorage.getItem('theme') ?? 'light'
}
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
return 'dark'
}
return 'light'
})()
if (theme === 'light') { if (theme === undefined) {
document.documentElement.setAttribute('data-theme', 'light') theme = window.matchMedia('(prefers-color-scheme: dark)').matches
} else { ? 'dark'
document.documentElement.setAttribute('data-theme', 'dark') : 'light'
} }
window.localStorage.setItem('theme', theme) localStorage.theme = theme
document.documentElement.dataset.theme = theme
}
setDarkMode(document)
document.addEventListener('astro:before-swap', (event) => {
setDarkMode(event.newDocument)
})
</script> </script>
<html lang="en"> <html lang="en" transition:name="root" transition:animate="none">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta <meta
@ -69,6 +72,7 @@ import Footer from '../components/Footer.astro'
title="Zen Browser Release Notes" title="Zen Browser Release Notes"
href={`${Astro.url.origin}/feed.xml`} href={`${Astro.url.origin}/feed.xml`}
/> />
<ClientRouter />
</head> </head>
<body <body
class="overflow-x-hidden bg-paper font-['bricolage-grotesque'] text-dark" class="overflow-x-hidden bg-paper font-['bricolage-grotesque'] text-dark"

View file

@ -261,6 +261,7 @@ const appleIcon = icon({ prefix: 'fab', iconName: 'apple' })
</main> </main>
</Layout> </Layout>
<script> <script>
document.addEventListener('astro:page-load', () => {
const releases = { const releases = {
macos: { macos: {
intel: 'zen.macos-x86_64.dmg', intel: 'zen.macos-x86_64.dmg',
@ -353,7 +354,9 @@ const appleIcon = icon({ prefix: 'fab', iconName: 'apple' })
"input[type='radio']:checked", "input[type='radio']:checked",
) as HTMLInputElement ) as HTMLInputElement
selectedArch = selectedRadio.value selectedArch = selectedRadio.value
document.getElementById('linux-target-download')?.classList.add('hidden') document
.getElementById('linux-target-download')
?.classList.add('hidden')
const linuxDownload = document.getElementById( const linuxDownload = document.getElementById(
'form-linux-download', 'form-linux-download',
@ -388,7 +391,9 @@ const appleIcon = icon({ prefix: 'fab', iconName: 'apple' })
if (selectedArch) { if (selectedArch) {
// Go back to architecture selection // Go back to architecture selection
if (selectedOS === 'linux') { if (selectedOS === 'linux') {
document.getElementById('form-linux-download')?.classList.add('hidden') document
.getElementById('form-linux-download')
?.classList.add('hidden')
document document
.getElementById('linux-target-download') .getElementById('linux-target-download')
?.classList.remove('hidden') ?.classList.remove('hidden')
@ -404,7 +409,9 @@ const appleIcon = icon({ prefix: 'fab', iconName: 'apple' })
} else if (selectedOS) { } else if (selectedOS) {
// Go back to OS selection // Go back to OS selection
if (selectedOS === 'macos') { if (selectedOS === 'macos') {
document.getElementById('form-macos-download')?.classList.add('hidden') document
.getElementById('form-macos-download')
?.classList.add('hidden')
} else if (selectedOS === 'linux') { } else if (selectedOS === 'linux') {
document document
.getElementById('linux-target-download') .getElementById('linux-target-download')
@ -512,6 +519,7 @@ const appleIcon = icon({ prefix: 'fab', iconName: 'apple' })
}) })
filloutDefaultOS() filloutDefaultOS()
})
</script> </script>
<style is:global> <style is:global>
@keyframes fadeIn { @keyframes fadeIn {