提高网站速度的一种简单方法是仅在需要时下载图片,也就是图片进入视窗时。这种“懒加载”技术已经存在一段时间了,并且有很多优秀 的 教程介绍如何实现它。
但是即使有了所有这些资源,懒加载的实现方式也可能因你正在使用的项目或框架而异。在本文中,我将使用 Intersection Observer API 以及 onLoad
事件来使用 Svelte JavaScript 框架懒加载图片。
如果你不熟悉该框架,请查看Tristram Tolliday 对 Svelte 的介绍。
让我们使用一个真实的例子
我在测试我正在进行的 Svelte 和 Sapper 应用程序 Shop Ireland 的速度时,将这种方法整合在一起。我们的目标之一是尽可能快地完成这项工作。我们发现主页的性能受到了影响,因为浏览器下载了大量不在屏幕上的图片,因此我们自然而然地转向了懒加载。
Svelte 已经非常快,因为所有代码都是预先编译的。但是一旦我们为图片添加了懒加载,速度就大大提升了。
这就是我们要一起努力的内容。请随时从 GitHub 获取此演示的最终代码,并继续阅读了解其工作原理。
这是最终的结果
让我们快速启动 Svelte
你可能已经有一个你想使用的 Svelte 应用程序,但如果没有,让我们从命令行开始一个新的 Svelte 项目并在本地进行操作
npx degit sveltejs/template my-svelte-project
cd my-svelte-project
npm install
npm run dev
你现在应该有一个在 http://localhost:5000
上运行的入门应用程序。
添加 components 文件夹
最初的 Svelte 演示有一个 App.svelte
文件,但还没有组件。让我们设置此演示所需的组件。目前还没有 components 文件夹,所以让我们在 src
文件夹中创建一个。在这个文件夹里,创建一个 Image
文件夹——它将容纳我们此演示的组件。
我们将让我们的组件做两件事。首先,他们将检查图片何时进入视窗。然后,当图片进入时,组件将等待图片文件加载完毕再显示它。
第一个组件将是一个 <IntersectionObserver>
,它将包装第二个组件,即 <ImageLoader>
。我喜欢这种设置的原因是它允许每个组件专注于做一件事,而不是试图在一个组件中打包一堆操作。
让我们从 <IntersectionObserver>
组件开始。
观察交集
我们的第一个组件将是 Intersection Observer API 的一个工作实现。Intersection Observer 非常复杂,但其本质是它监视子元素,并在子元素进入其父元素的边界框时通知我们。因此,对于图片来说:它们可以是某个父元素的子元素,当它们滚动到视图中时,我们可以得到提示。
虽然深入了解 Intersection Observer API 的进进出出是一个好主意——Travis Almand 对其有一个优秀的介绍——但我们将使用一个方便的 Svelte 组件,它是 Rich Harris 为svelte.dev 准备的。
我们将先设置它,然后再深入研究它的具体工作原理。创建一个新的 IntersectionObserver.svelte
文件,并将其放入 src/components/Image
文件夹中。我们将在这里定义具有以下代码的组件
<script>
import { onMount } from 'svelte';
export let once = false;
export let top = 0;
export let bottom = 0;
export let left = 0;
export let right = 0;
let intersecting = false;
let container;
onMount(() => {
if (typeof IntersectionObserver !== 'undefined') {
const rootMargin = `${bottom}px ${left}px ${top}px ${right}px`;
const observer = new IntersectionObserver(entries => {
intersecting = entries[0].isIntersecting;
if (intersecting && once) {
observer.unobserve(container);
}
}, {
rootMargin
});
observer.observe(container);
return () => observer.unobserve(container);
}
// The following is a fallback for older browsers
function handler() {
const bcr = container.getBoundingClientRect();
intersecting = (
(bcr.bottom + bottom) > 0 &&
(bcr.right + right) > 0 &&
(bcr.top - top) < window.innerHeight &&
(bcr.left - left) < window.innerWidth
);
if (intersecting && once) {
window.removeEventListener('scroll', handler);
}
}
window.addEventListener('scroll', handler);
return () => window.removeEventListener('scroll', handler);
});
</script>
<style>
div {
width: 100%;
height: 100%;
}
</style>
<div bind:this={container}>
<slot {intersecting}></slot>
</div>
我们可以使用此组件作为其他组件的包装器,它将为我们确定包装的组件是否与视窗相交。
如果你熟悉 Svelte 组件的结构,你会发现它遵循一个模式,从脚本开始,然后进入样式,最后是标记。它设置了一些我们可以传递的选项,包括 once
属性,以及屏幕边缘的顶部、右侧、底部和左侧距离的数值,这些数值定义了交集开始的点。
我们将忽略这些距离,而是使用 once
属性。这将确保图片仅在它们第一次进入视窗时加载。
组件的主要逻辑位于 onMount
部分。这将设置我们的观察器,用于检查我们的元素以确定它是否与屏幕的可见区域“相交”。
对于旧浏览器,它还会附加一个滚动事件,以检查我们在滚动时元素是否可见,然后如果我们确定它可用且 once
为 true
,它将移除这个侦听器。
加载图片
让我们使用 <IntersectionObserver>
组件通过将其包装在 <ImageLoader>
组件周围来有条件地加载图片。同样,这个组件会收到来自 <IntersectionOberserver>
的通知,以便它知道何时该加载图片。
这意味着我们需要在 components/Image
中创建一个新的组件文件。让我们把它命名为 ImageLoader.svelte
。以下是我们要在其中写入的代码
<script>
export let src
export let alt
import IntersectionObserver from './IntersectionObserver.svelte'
import Image from './Image.svelte'
</script>
<IntersectionObserver once={true} let:intersecting={intersecting}>
{#if intersecting}
<Image {alt} {src} />
{/if}
</IntersectionObserver>
这个组件接受一些与图片相关的道具——src
和 alt
——我们将在其中使用它们来创建图片的实际标记。请注意,我们在脚本部分导入了两个组件,包括我们刚刚创建的 <IntersectionObserver>
和另一个我们尚未创建的组件,名为 <Image>
,我们稍后会介绍。
<IntersectionObserver>
通过作为即将创建的 <Image>
组件的包装器来发挥作用。请查看其上的那些属性。我们正在将 once
设置为 true
,因此图片仅在第一次看到时加载。
然后我们使用 Svelte 的插槽道具。它们是什么?让我们接下来介绍它们。
插槽属性值
包装组件(比如我们的 <IntersectionObserver>
)非常适合将道具传递给它包含的子元素。Svelte 为我们提供了名为“插槽道具”的东西,以实现这一点。
在我们的 <IntersectionObserver>
组件中,你可能已经注意到这一行
<slot {intersecting}></slot>
它将 intersecting 道具传递到我们给它的任何组件。在这种情况下,当 <ImageLoader>
组件使用包装器时,它会接收到该道具。我们可以使用 let:intersecting={intersecting}
来访问该道具,就像这样
<IntersectionObserver once={true} let:intersecting={intersecting}>
然后我们可以使用 intersecting 值来确定何时该加载 <Image>
组件。在本例中,我们使用一个 if 条件来检查何时该执行。
<IntersectionObserver once={true} let:intersecting={intersecting}>
{#if intersecting}
<Image {alt} {src} />
{/if}
</IntersectionObserver>
如果发生了交集,<Image>
将被加载并接收 alt
和 src
道具。你可以在这篇Svelte 教程 中了解更多关于插槽道具的信息。
我们现在已经到位,可以在图片滚动到屏幕上时显示 <Image>
组件。最后,让我们来构建这个组件。
加载时显示图片
是的,你猜对了:让我们将 Image.svelte
文件添加到 components/Image
文件夹中,用于我们的 <Image>
组件。这个组件会接收我们的 alt
和 src
道具,并将它们设置在 <img>
元素上。
以下是组件代码
<script>
export let src
export let alt
import { onMount } from 'svelte'
let loaded = false
let thisImage
onMount(() => {
thisImage.onload = () => {
loaded = true
}
})
</script>
<style>
img {
height: 200px;
opacity: 0;
transition: opacity 1200ms ease-out;
}
img.loaded {
opacity: 1;
}
</style>
<img {src} {alt} class:loaded bind:this={thisImage} />
首先,我们在定义两个新变量之前接收了alt
和 src
属性:loaded
用于存储图像是否已加载,thisImage
用于存储对 img DOM 元素本身的引用。
我们还使用了一个名为 onMount
的 Svelte 有用方法。这为我们提供了一种在组件在 DOM 中呈现后调用函数的方法。在这种情况下,我们为 thisImage.onload
设置了一个回调函数。简单地说,这意味着它在图像加载完成后执行,并将 loaded
变量设置为 true
值。
我们将使用 CSS 来显示图像并将其淡入视图。让我们为图像设置 opacity: 0
,以便它们最初不可见,尽管从技术上讲它们在页面上。然后,当它们与视窗相交并且 <ImageLoader>
允许加载图像时,我们将图像设置为完全不透明。我们可以通过为图像设置 transition
属性来使其平滑过渡。演示将过渡时间设置为 1200 毫秒,但您可以根据需要加快或减慢速度。
这将我们引向文件中的最后一行,它是 <img>
元素的标记。
<img {src} {alt} class:loaded bind:this={thisImage} />
这使用 class:loaded
来有条件地应用 .loaded
类,如果 loaded 变量为 true
。它还使用 bind:this
方法将此 DOM 元素与 thisImage
变量关联。
原生延迟加载
虽然对浏览器中原生延迟加载的支持即将到来,但它还没有在所有当前稳定版本中得到支持。我们仍然可以使用简单的功能检查来添加对它的支持。
在我们的 ImageLoader.svelte
文件中,我们可以引入 onMount
函数,并在其中检查我们的浏览器是否支持延迟加载。
import { onMount } from 'svelte'
let nativeLoading = false
// Determine whether to bypass our intersecting check
onMount(() => {
if ('loading' in HTMLImageElement.prototype) {
nativeLoading = true
}
})
然后,我们调整 if
条件以包含此 nativeLoading
布尔值。
{#if intersecting || nativeLoading}
<Image {alt} {src} />
{/if}
最后,在 Image.svelte
中,我们告诉浏览器使用延迟加载,方法是在 <img>
元素中添加 loading="lazy"
。
<img {src} {alt} class:loaded bind:this={thisImage} loading="lazy" />
这使现代和未来的浏览器能够绕过我们的代码并原生处理延迟加载。
让我们将它们全部连接起来!
好了,现在是实际使用我们组件的时候了。打开 App.svelte
文件,并将以下代码放入其中以导入我们的组件并使用它
<script>
import ImageLoader from './components/Image/ImageLoader.svelte';
</script>
<ImageLoader src="OUR_IMAGE_URL" alt="Our image"></ImageLoader>
这是演示的再次展示
请记住,您可以在 GitHub 上下载此演示的完整代码。如果您想在生产网站上看到它,请查看我的Shop Ireland 项目。延迟加载在首页、类别页面和搜索页面上使用,以帮助加快速度。我希望您能将它用于您自己的 Svelte 项目!
没有提到图像的原生延迟加载?https://caniuse.cn/loading-lazy-attr
为什么要使用滚动侦听器?我认为 IntersectionObserver 的目的是不使用滚动侦听器?
滚动侦听器似乎是那些不支持 IntersectionObserver 的浏览器的后备方案。
很棒的文章!代码超级干净,对初学者和高级开发人员来说解释得非常好。非常感谢!
这会产生相同的效果….
const lazyLoading = (node) => {
node.onload = () => {
node.style.opacity = 1;
}
}
img {
width: 100%;
opacity: 0;
transition: 1s opacity;
}
该解决方案用于延迟加载图像,而不是显示方式。
假设您在页面上拥有 100 万张图像。如果没有延迟加载,浏览器将疯狂地加载所有图像,导致页面无法使用。您的解决方案将加载所有图像并仅更改不透明度。
非常感谢!多么棒的文章。它完美无缺<3
在
image.svelte
中,使用动作(<img use:thisImage>
)在 img 标签上而不是使用 onMount 和 thisImage 图像元素,这样可以吗?
谢谢,我已经弄清楚如何在 Svelte 中使用延迟加载。如何延迟加载组件?也就是说,使滚动条在图像/组件加载时增长