Lynx 提供了丰富的动效功能,使得开发者能够在 Lynx 中构建出更加现代、流畅且易于理解的用户界面。利用这些动效功能,可以打造出绚丽的过渡效果、自然的动效反馈,从而提升用户体验。
如果你需要对布局或样式在发生变化时平滑地应用新的属性值,可以使用过渡动画。
过渡动画提供了一种在更改 CSS 属性时控制动画速度的方法。与属性更改立即生效不同,你可以让属性的更改在一段时间内逐渐发生。例如,当你将一个元件的颜色从白色变为黑色时,通常该更改是瞬间完成的。而启用过渡动画后,变化将在一个符合加速曲线的时间间隔内发生,这一切都可以进行自定义。
过渡动画具有自动触发、非重复播放、简单易用的特点,可以通过复合 transition
来定义动画属性和时长,也可以使用单条形式的 transition-property
和 transition-duration
等来指定。
你可以使用过渡效果为列表中一个列表项添加从收起到展开的动画:
import { root } from "@lynx-js/react";
import { useState } from "react";
import "./index.scss";
const AccordionItem = ({ title, content }) => {
const [isActive, setIsActive] = useState(false);
const toggleItem = () => {
setIsActive(!isActive);
};
return (
<view className={`accordion-item ${isActive ? "active" : ""}`}>
<view className="accordion-header" bindtap={toggleItem} style={{ transition: "background-color 0.5s" }}>
<text>{title}</text>
<text className={`icon ${isActive ? "rotate" : ""}`} style={{ transition: "transform 0.5s" }}>▼</text>
</view>
<view
className="accordion-content"
// use 200px to estimate the height
style={{ maxHeight: isActive ? "200px" : "14px", transition: "max-height 2s linear" }}
>
<text className="content">{content}</text>
</view>
</view>
);
};
const Accordion = () => {
const items = [
{
title: "Item 1",
content: "This is the content of item 1. ".repeat(10),
},
{
title: "Item 2",
content: "This is the content of item 2. ".repeat(10),
},
{
title: "Item 3",
content: "This is the content of item 3. ".repeat(10),
},
];
return (
<view className="accordion">
{items.map((item, index) => <AccordionItem key={index} title={item.title} content={item.content} />)}
</view>
);
};
root.render(<Accordion />);
if (import.meta.webpackHot) {
import.meta.webpackHot.accept();
}
如果你需要多组样式配置按序列过渡的效果,可以使用关键帧动画。
关键帧动画通过 CSS 中的 @keyframes
规则定义,指定动画各个阶段的样式变化;并使用 animation
属性可以将定义好的动画应用到元件上,并设置动画名称、持续时间、延迟、播放次数等参数。
关键帧动画相较过渡动画更为灵活和可控,允许你指定过程以及更细粒度的时间曲线,可以在 CSS 中定义 @keyframes
,然后通过复合 animation
属性来指定,也可以使用单条形式的 animation-name
、animation-duration
等属性进行定义。
通过关键帧动画,我们可以实现一个边框旋转的效果。
page {
background-color: #f7f7f7;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
.container {
border: 1px solid red;
}
.title-name-wrapper-border {
position: relative;
z-index: 0;
width: 100%;
height: 100%;
border-radius: 10px;
overflow: hidden;
padding: 2rem;
}
@keyframes rotate {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(1turn);
}
}
@keyframes opacityChange {
50% {
opacity: 1;
}
100% {
opacity: 0.5;
}
}
.container {
position: relative;
z-index: 0;
width: 80%;
height: 300px;
border-radius: 10px;
overflow: hidden;
}
.title-name-wrapper-border-before {
position: absolute;
z-index: -2;
left: -50%;
top: -50%;
width: 200%;
height: 200%;
background-color: red;
background-repeat: no-repeat, no-repeat, no-repeat, no-repeat;
background-size: 50% 50%, 50% 50%;
background-position: 0px 0px, 100% 0px, 100% 100%, 0px 100%;
background-image:
linear-gradient(#399953, #399953),
linear-gradient(#fbb300, #fbb300),
linear-gradient(#d53e33, #d53e33),
linear-gradient(#377af5, #377af5);
animation: rotate 4s linear infinite;
}
.title-name-wrapper-border-after {
position: absolute;
z-index: -1;
left: 6px;
top: 6px;
width: calc(100% - 12px);
height: calc(100% - 12px);
background: white;
border-radius: 5px;
animation: opacityChange 3s infinite alternate;
}
通过关键帧动画,我们可以为元件的运动添加符合弹簧物理效果的动画。
.container {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #f5f5f5;
}
.spring-box {
width: 200px;
height: 200px;
background-color: #4a90e2;
border-radius: 8px;
display: flex;
justify-content: center;
align-items: center;
}
.box-text {
color: white;
font-size: 24px;
}
.animate {
animation: kf-anim-spring 4s linear forwards;
}
@keyframes kf-anim-spring {
0% {
transform: scale(0, 0);
}
0.999% {
transform: scale(0.306, 0.306);
}
1.998% {
transform: scale(0.967, 0.967);
}
2.997% {
transform: scale(1.586, 1.586);
}
3.996% {
transform: scale(1.825, 1.825);
}
4.995% {
transform: scale(1.586, 1.586);
}
5.994% {
transform: scale(1.046, 1.046);
}
6.993% {
transform: scale(0.529, 0.529);
}
7.992% {
transform: scale(0.319, 0.319);
}
9.09% {
transform: scale(0.54, 0.54);
}
10.089% {
transform: scale(0.993, 0.993);
}
11.088% {
transform: scale(1.409, 1.409);
}
12.087% {
transform: scale(1.561, 1.561);
}
13.086% {
transform: scale(1.389, 1.389);
}
14.085% {
transform: scale(1.018, 1.018);
}
15.084% {
transform: scale(0.671, 0.671);
}
16.083% {
transform: scale(0.537, 0.537);
}
17.082% {
transform: scale(0.67, 0.67);
}
18.081% {
transform: scale(0.973, 0.973);
}
19.08% {
transform: scale(1.263, 1.263);
}
20.079% {
transform: scale(1.381, 1.381);
}
21.178% {
transform: scale(1.258, 1.258);
}
22.177% {
transform: scale(1.003, 1.003);
}
23.176% {
transform: scale(0.77, 0.77);
}
24.175% {
transform: scale(0.685, 0.685);
}
25.174% {
transform: scale(0.781, 0.781);
}
26.173% {
transform: scale(0.989, 0.989);
}
27.172% {
transform: scale(1.184, 1.184);
}
28.171% {
transform: scale(1.259, 1.259);
}
29.17% {
transform: scale(1.185, 1.185);
}
30.169% {
transform: scale(1.015, 1.015);
}
31.168% {
transform: scale(0.852, 0.852);
}
32.167% {
transform: scale(0.785, 0.785);
}
33.266% {
transform: scale(0.855, 0.855);
}
34.265% {
transform: scale(0.997, 0.997);
}
35.264% {
transform: scale(1.128, 1.128);
}
36.263% {
transform: scale(1.176, 1.176);
}
38.261% {
transform: scale(1.006, 1.006);
}
40.259% {
transform: scale(0.854, 0.854);
}
42.257% {
transform: scale(0.991, 0.991);
}
44.255% {
transform: scale(1.12, 1.12);
}
46.353% {
transform: scale(1.001, 1.001);
}
48.351% {
transform: scale(0.9, 0.9);
}
52.347% {
transform: scale(1.081, 1.081);
}
56.343% {
transform: scale(0.932, 0.932);
}
60.439% {
transform: scale(1.055, 1.055);
}
64.435% {
transform: scale(0.954, 0.954);
}
68.431% {
transform: scale(1.037, 1.037);
}
72.527% {
transform: scale(0.968, 0.968);
}
76.523% {
transform: scale(1.025, 1.025);
}
80.519% {
transform: scale(0.978, 0.978);
}
84.615% {
transform: scale(1.017, 1.017);
}
88.611% {
transform: scale(0.985, 0.985);
}
92.607% {
transform: scale(1.011, 1.011);
}
96.703% {
transform: scale(0.99, 0.99);
}
100% {
transform: scale(1.006, 1.006);
}
}
此外,我们提供的 animate api 扩展了 CSS 关键帧动画的应用范围,使你能够在 JavaScript 代码中更加灵活和动态地创建和控制动画。通过 animate 开发者能够在运行时根据交互或逻辑条件动态生成和修改动画,为用户提供更加生动和互动的体验。
通过使用 animate api,为原件增加一个可以实时变化速率的动效。
import { root } from "@lynx-js/react";
import "./index.scss";
const AnimateAnimationExample = () => {
return (
<view style={{ width: "100%", height: "100%" }}>
<view
id="view1"
class="box"
bindtap={() => {
const ani = lynx.getElementById("view1").animate(
[
{
transform: "translate(0%, 0%) rotate(0deg)",
"animation-timing-function": "linear",
},
{
transform: "translate(200px, 0%) rotate(90deg)",
"animation-timing-function": "cubic-bezier(.91,.03,.94,.11)",
},
{
transform: "translate(200px, 100%) rotate(180deg)",
"animation-timing-function": "linear",
},
{
transform: "translate(0%, 100%) rotate(270deg)",
"animation-timing-function": "cubic-bezier(.91,.03,.94,.11)",
},
{
transform: "translate(0%, 0%) rotate(360deg)",
},
],
{
name: "js-animation-1",
duration: 5000,
iterations: Infinity,
easing: "linear",
},
);
}}
>
</view>
</view>
);
};
root.render(<AnimateAnimationExample />);
if (import.meta.webpackHot) {
import.meta.webpackHot.accept();
}