本文主要介绍了古卷轴展开动画的实现,包括 jquery 版本和 react 版本,还实现了从中间展开的动画效果和从右向左展开的动画效果。

当然,还可以结合古诗词淡入淡出动画效果,实现古诗词淡入动画和古画卷展开动画的组合效果。

jquery 版本

在网上找了一个 jquery 版本的实现,具体 demo 可以查看下列网址:古卷轴平滑打开 jQuery 动画特效

动画实现代码如下所示,直接从 demo 中复制过来,直接使用即可。

 1$(document).ready(function () {
 2  
 3  $(".l-pic-index").animate({ left: "95px", top: "-4px" }, 1300);
 4  $(".r-pic-index").animate({ right: "-23px", top: "-5px" }, 1450);
 5  $(".l-bg-index").animate({ width: "433px", left: "73px" }, 1500);
 6  $(".r-bg-index").animate(
 7    { width: "433px", right: "-38px" },
 8    1500,
 9    function () {
10      $(".main-index").fadeIn(800);
11    }
12  );
13});

react 版本

上述是用 jquery 实现的版本,但是一般现在开发都用 vue 和 react 框架了,不使用 jquery 了。

我的项目是使用 react 开发的,因此使用 react 重构一下,在 react 项目中实现该功能,整体而言,调整的东西也不多。

1. 初始状态:两个卷轴

固定初始状态的位置:只显示两个卷轴,画卷和内容部分不显示

 1function Page() {
 2  return (
 3    <div className="reel_content">
 4      <div className="l-pic-index"></div>
 5      <div className="r-pic-index"></div>
 6      <div className="l-bg-index"></div>
 7      <div className="r-bg-index"></div>
 8      <div className="main-index">
 9        <p className="intro-text">xxx</p>
10      </div>
11    </div>
12  );
13}
14export default Page;
 1.reel_content {
 2  position: relative;
 3  z-index: 99;
 4  width: 900px;
 5  height: 460px;
 6  padding: 40px 0;
 7  background-color: #6f0b02;
 8  box-sizing: border-box;
 9  .l-pic-index {
10    position: absolute;
11    left: 400px;
12    top: 1px;
13    z-index: 2;
14    width: 50px;
15    height: 460px;
16    background: url("../img/j1.png") no-repeat right 0;
17  }
18  .r-pic-index {
19    position: absolute;
20    right: 400px;
21    top: 0;
22    z-index: 2;
23    width: 50px;
24    height: 460px;
25    background: url("../img/j4.png") no-repeat left 0;
26  }
27  .l-bg-index {
28    position: absolute;
29    top: -3px;
30    left: 430px;
31    z-index: 1;
32    width: 25px;
33    height: 459px;
34    background: url("../img/j2.png") right 0 no-repeat;
35  }
36  .r-bg-index {
37    position: absolute;
38    top: -4px;
39    right: 430px;
40    z-index: 1;
41    width: 25px;
42    height: 459px;
43    background: url("../img/j3.png") 0 0 no-repeat;
44  }
45
46  .main-index {
47    display: none;
48    overflow: hidden;
49    zoom: 1;
50    position: absolute;
51    z-index: 5;
52    width: 530px;
53    height: 280px;
54    left: 145px;
55    top: 90px;
56    color: #2e2e2e;
57  }
58}

初始状态效果如下所示:

2. 结束状态:全部展开

固定所有内容的位置,显示卷轴中部和内容部分

 1.reel_content_finish {
 2  .l-pic-index {
 3    left: 95px !important;
 4    top: -4px !important;
 5  }
 6  .r-pic-index {
 7    right: -23px !important;
 8    top: -5px !important;
 9  }
10  .l-bg-index {
11    width: 433px !important;
12    left: 73px !important;
13  }
14  .r-bg-index {
15    width: 433px !important;
16    right: -38px !important;
17  }
18  .main-index {
19    display: block !important;
20  }
21}

结束状态效果如下所示:

3. 卷轴展开:中间展开

将 jquery 版本的动画效果代码,转换一下为 react 的,如下所示:

 1function Page() {
 2  
 3  const [isAnimating, setIsAnimating] = useState(false);
 4  
 5  const [mainIndexStyle, setMainIndexStyle] = useState({ display: "none" });
 6  
 7  const [lPicIndexStyle, setLPicIndexStyle] = useState({});
 8  
 9  const [rPicIndexStyle, setRPicIndexStyle] = useState({});
10  
11  const [lBgIndexStyle, setLBgIndexStyle] = useState({});
12  
13  const [rBgIndexStyle, setRBgIndexStyle] = useState({});
14  
15  const [mainIndexVisible, setMainIndexVisible] = useState(false);
16
17  useEffect(() => {
18    
19    const intervalId = setInterval(() => {
20      setIsAnimating(true);
21    }, 3000);
22
23    return () => clearInterval(intervalId); 
24  }, []);
25
26  useEffect(() => {
27    
28    if (isAnimating) {
29      animateElements();
30    }
31  }, [isAnimating]);
32
33  useEffect(() => {
34    
35    if (mainIndexVisible) {
36      setTimeout(() => {
37        setMainIndexStyle({ display: "block" });
38      }, 800);
39    }
40  }, [mainIndexVisible]);
41
42  
43  const animateElements = () => {
44    const lPicIndexStyle = {
45      left: "95px",
46      top: "-4px",
47      transition: "left 1300ms, top 1300ms",
48    };
49    const rPicIndexStyle = {
50      right: "-23px",
51      top: "-5px",
52      transition: "right 1450ms, top 1450ms",
53    };
54    const lBgIndexStyle = {
55      width: "433px",
56      left: "73px",
57      transition: "width 1500ms, left 1500ms",
58    };
59    const rBgIndexStyle = {
60      width: "433px",
61      right: "-38px",
62      transition: "width 1500ms, right 1500ms",
63    };
64
65    
66    setTimeout(() => {
67      setMainIndexVisible(true);
68
69      
70      setTimeout(() => {
71        resetAnimations();
72      }, 3000);
73    }, 1500);
74
75    
76    setLPicIndexStyle(lPicIndexStyle);
77    setRPicIndexStyle(rPicIndexStyle);
78    setLBgIndexStyle(lBgIndexStyle);
79    setRBgIndexStyle(rBgIndexStyle);
80  };
81
82  
83  const resetAnimations = () => {
84    setMainIndexVisible(false);
85    setMainIndexStyle({ display: "none" });
86    setLPicIndexStyle({});
87    setRPicIndexStyle({});
88    setLBgIndexStyle({});
89    setRBgIndexStyle({});
90    setIsAnimating(false);
91  };
92
93  return (
94    <div className="reel_content">
95      <div className="l-pic-index" style={{ ...lPicIndexStyle }}></div>
96      <div className="r-pic-index" style={{ ...rPicIndexStyle }}></div>
97      <div className="l-bg-index" style={{ ...lBgIndexStyle }}></div>
98      <div className="r-bg-index" style={{ ...rBgIndexStyle }}></div>
99      <div className="main-index" style={{ ...mainIndexStyle }}>
100        <p className="intro-text">xxx</p>
101      </div>
102    </div>
103  );
104}
105
106export default memo(Page);

我的版本(右向左展开)

上述实现的实现的版本是:从中间向两边展开的。不满足项目需求,所以需要修改。

而且上述的实现代码太复杂、太繁琐了,需要简化一下,直接使用 CSS3 动画就行了。

我的项目需求为:卷轴滚动时,从右向左打开,并且同步出现诗句

前提条件:将图片 j2.png 和 j3.png 合并成一张完整的图片

精简的代码,如下所示:

 1function Page() {
 2  
 3  const [isAnimating, setIsAnimating] = useState(false);
 4  
 5  const [reelClass, setReelClass] = useState("reel_wrap");
 6
 7  useEffect(() => {
 8    const intervalId = setTimeout(() => {
 9      setIsAnimating(true);
10    }, 3000);
11
12    return () => clearTimeout(intervalId); 
13  }, []);
14
15  useEffect(() => {
16    
17    if (isAnimating) {
18      setReelClass("reel_wrap reel_wrap_finish");
19
20      
21      
22      
23      
24      
25    }
26  }, [isAnimating]);
27
28  return (
29    <div className={reelClass}>
30      <div className="reel_l"></div>
31      <div className="reel_r"></div>
32      <div className="reel_bg"></div>
33    </div>
34  );
35}
36
37export default memo(Page);

样式实现:

 1$reelWidth: 48px; 
 2$reelHeight: 384px; 
 3$reelBgWidth: 864px; 
 4$time: 3000ms;
 5
 6.reel_wrap {
 7  position: absolute;
 8  z-index: 99;
 9  width: $reelWidth * 2 + $reelBgWidth;
10  height: $reelHeight;
11  background-color: #6f0b02;
12  box-sizing: border-box;
13  overflow: hidden;
14  &_finish {
15    
16    .reel_l {
17      left: 0 !important;
18      transition: left $time ease-in-out;
19    }
20    .reel_bg {
21      width: $reelBgWidth !important;
22      left: $reelWidth !important;
23      transition: width $time ease-in-out, left $time ease-in-out;
24    }
25  }
26  .reel_l {
27    position: absolute;
28    z-index: 2;
29    left: $reelBgWidth;
30    width: $reelWidth;
31    height: $reelHeight;
32    background: url("../img/j1.png") 0 0 / 100% 100% no-repeat;
33  }
34  .reel_r {
35    position: absolute;
36    z-index: 2;
37    right: 0;
38    width: $reelWidth;
39    height: $reelHeight;
40    background: url("../img/j4.png") 0 0 / 100% 100% no-repeat;
41  }
42  .reel_bg {
43    position: absolute;
44    z-index: 11;
45    left: $reelWidth + $reelBgWidth;
46    width: 0;
47    height: $reelHeight;
48    background: url("../img/j5.png") right 0 no-repeat; 
49  }
50}

其他系列文章:

个人笔记记录 2021 ~ 2025