前端开发 / React Masonry 瀑布流布局进阶:添加入场动画

这篇文章为你提供实用的代码和工具,帮助你轻松地在 React 应用中添加瀑布流布局和动画效果。

前端开发 / React Masonry 瀑布流布局进阶:添加入场动画

这篇文章详细介绍了如何为你的网站的瀑布流布局元素添加入场动画效果(其他布局也适用),这可以将用户体验提升到下一个阶段。

如果你还没有为网站实现瀑布流布局,你可以先添加瀑布流布局效果,可以参考我的这篇文章:

最终实现效果

0:00
/0:03

能够仅通过 CSS 实现吗?

使用纯 CSS 来检测元素是否进入视窗并应用动画是不可能的,因为 CSS 本身不能“感知”元素是否进入了视窗。

因此,我们可以通过 CSS 来实现动画效果,并使用 JavaScript 来监听元素何时进入视窗范围。

使用 CSS 添加动画效果

以下代码实现【从底部滑入】的效果:

@keyframes slideInFromBottom {
    0% {
        transform: translateY(30%);  // 初始时,元素向下偏移 30%
        opacity: 0.5;               // 初始透明度为 0.5
    }
    100% {
        transform: translateY(0);   // 结束时,元素返回原位置
        opacity: 1;                // 完全不透明
    }
}

.slide-in {
    animation-name: slideInFromBottom;  // 使用上述定义的动画
    animation-duration: 0.5s;           // 动画时长 0.5 秒
    animation-fill-mode: forwards;      // 动画结束后保持最后状态
    animation-timing-function: ease-in-out;  // 动画速度先慢后快再慢
    will-change: transform, opacity;    // 预告浏览器即将改变的属性,以优化性能
}
0:00
/0:02


你还可以添加一些弹簧效果,使动画看起来更加自然,例如使用 cubic-bezier 函数。

@keyframes slideInFromBottom {
    0% {
        transform: translateY(30%);  // 初始时,元素向下偏移 30%
        opacity: 0.5;               // 初始透明度为 0.5
    }
    100% {
        transform: translateY(0);   // 结束时,元素返回原位置
        opacity: 1;                // 完全不透明
    }
}

.slide-in {
  animation-name: slideInFromBottom;
  animation-duration: 0.5s;
  animation-fill-mode: forwards;
  animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1.275);
  will-change: transform, opacity;
}
0:00
/0:04

这里的 cubic-bezier(0.175, 0.885, 0.32, 1.275) 是一个经典的弹跳效果,你可以根据需要微调这些值。

如果你想进一步研究或调整 Bezier 曲线,cubic-bezier.com 是一个很好的工具,它允许您可视化和自定义曲线,并即时看到效果。


如果需要更过的动画效果,可以参考以下演示网站以及对应的 CSS 文件:

使用 JavaScript 添加触发规则

react-intersection-observer 是一个专门为 React 开发的工具,用于监听一个组件是否进入了视窗范围。

这个库基于浏览器的 Intersection Observer API ,并为 React 提供了一个简单的钩子( hook )来使用它。

使用方法非常简单的,首先导入 useInView 钩子:

import { useInView } from 'react-intersection-observer';

使用 useInView 钩子创建一个变量 inView :

const [ref, inView] = useInView({
        triggerOnce: true, // 只触发一次
        threshold: 0.1 // 当 10%的元素可见时触发
    });

以上代码解释:当元素的 10% 进入窗口时,变量 inView 值为 True ,否则为 False 。

最后,根据 inView 的值,在 <div> 元素内部渲染不同的 className :

import React from 'react';
import { useInView } from 'react-intersection-observer';
import { ImageListItem } from "@mui/material";
import Image from "next/image";

const InViewImageItem = ({ image, onClick }) => {
    const [ref, inView] = useInView({
        triggerOnce: true, // 只触发一次
        threshold: 0.1 // 当 10%的元素可见时触发
    });

    return (
        <ImageListItem
            ref={ref}
            style={{
                overflow: "hidden",
            }}
            className={inView ? 'fade-in' : ''}
            onClick={() => onClick(image)}
        >
            <Image
                src={image.url}
                alt={image.description}
                width={image.width}
                height={image.height}
            />
        </ImageListItem>
    );
}

export default InViewImageItem;

react-intersection-observer 使用方法就是这么简单!