动效

介绍

Lynx 提供了丰富的动效功能,使得开发者能够在 Lynx 中构建出更加现代、流畅且易于理解的用户界面。利用这些动效功能,可以打造出绚丽的过渡效果、自然的动效反馈,从而提升用户体验。

为布局和样式的变化添加过渡动画

如果你需要对布局或样式在发生变化时平滑地应用新的属性值,可以使用过渡动画。

过渡动画提供了一种在更改 CSS 属性时控制动画速度的方法。与属性更改立即生效不同,你可以让属性的更改在一段时间内逐渐发生。例如,当你将一个元件的颜色从白色变为黑色时,通常该更改是瞬间完成的。而启用过渡动画后,变化将在一个符合加速曲线的时间间隔内发生,这一切都可以进行自定义。

过渡动画具有自动触发、非重复播放、简单易用的特点,可以通过复合 transition 来定义动画属性和时长,也可以使用单条形式的 transition-propertytransition-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-nameanimation-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);
  }
}

在 JS 中灵活创建关键帧动画

此外,我们提供的 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();
}
除非另有说明,本项目采用知识共享署名 4.0 国际许可协议进行许可,代码示例采用 Apache License 2.0 许可协议进行许可。