不规则曲线动画

发表于 2023-06-12 07:03:54
更新于 2024-04-18 13:33:37
CSS

文字按不规则曲线排布

利用 svg 的功能我们可以实现文字的不规则排布。

我有一只小毛驴,我从来也不骑。有一天我心血来潮骑他去赶集。我手里拿着小皮鞭,我心里正得意。
vue
<script setup lang="ts">

</script>

<template>
  <svg width="689px" height="255px" viewBox="0 0 689 255" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    <g id="页面1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
      <path id="p1" d="M1,10 C124.478963,264.492025 226.386169,319.875549 306.721619,176.150574 C387.057068,32.4255981 514.425822,-22.9579264 688.827881,10" :stroke="showSVGLine ? '#979797' : 'transparent'"></path>
    </g>
    <text>
      <textPath href="#p1">
        我有一只小毛驴,我从来也不骑。有一天我心血来潮骑他去赶集。我手里拿着小皮鞭,我心里正得意。
      </textPath>
    </text>
  </svg>
</template>

<style scoped>

</style>

按不规则路径运动

这个时候我们不需要 svg 了,但是我们提取其 path 作为 offset-path 的属性值。

动画的播放是通过控制 offset-distance

vue
<script setup lang="ts">

</script>

<template>
  <div class="wrapper">
    <div class="circle"></div>
  </div>
</template>

<style scoped>
.wrapper {
    height: 300px;
}

.circle {
    width: 20px;
    height: 20px;
    background-color: red;
    border-radius: 50%;

    offset-path: path('M1,10 C124.478963,264.492025 226.386169,319.875549 306.721619,176.150574 C387.057068,32.4255981 514.425822,-22.9579264 688.827881,10');
    animation: move 3s linear infinite;
}

@keyframes move {
  to {
    offset-distance: 100%;
  }
}
</style>

抛物线动画

实际上,有了上面那个例子的实现,那么抛物线动画当然是沿用上面的思路。

可是有些场景下,我们并不能提前确定,曲线是什么样的,比如:饿了么的添加购物车的效果。

这个时候,我们使用两个元素将横向和纵向的移动效果叠加起来实现会更方便。

由于我们的商品有很多,商品的列表一般是可以滚动的,所以动画起点是不确定的,动画的终点是确定的。

商品1
商品2
购物车
vue
<script setup lang="ts">
import { nextTick, onMounted, ref } from "vue";

const cart = ref<HTMLDivElement | null>(null);

function handleClick(e) {
  const boundBtn = e.target.getBoundingClientRect();
  const boundCart = cart.value!.getBoundingClientRect();

  const ballDom = document.createElement('span');
  ballDom.className = 'ball';
  const ballInnerDom = document.createElement('span');
  ballInnerDom.className = 'inner';

  ballDom.append(ballInnerDom);
  cart.value!.append(ballDom);

  const offsetX = boundCart.left + boundCart.width / 2 - (boundBtn.left + boundBtn.width / 2);
  const offsetY = boundCart.top + boundCart.height / 2 - (boundBtn.top + boundBtn.height / 2);

  ballDom!.style.display = 'block';
  ballDom!.style.transform = `translate3d(0, -240px, 0)`;
  ballInnerDom!.style.transform = `translate3d(${-offsetX}px, ${-(offsetY-240)}px, 0)`;
  let rt = ballDom!.offsetHeight;

  nextTick(() => {
    ballDom!.style.transform = `translate3d(0, 0, 0)`;
    ballInnerDom!.style.transform = `translate3d(0, 0, 0)`;
  });

  ballDom.addEventListener('transitionend', function() {
    ballDom.remove();
  });
}
</script>

<template>
  <div class="wrapper">
    <div class="product">
      <span>商品1</span>
      <button @click="handleClick">+</button>
    </div>
    <div class="product">
      <span>商品2</span>
      <button @click="handleClick">+</button>
    </div>
    <div class="cart" ref="cart">
      购物车
      <span class="ball">
        <span class="inner">
          <!-- 占个位,不写 CSS 貌似会被移除 -->
        </span>
      </span>
    </div>
  </div>
</template>

<style scoped>
.wrapper {
  position: relative;
  padding-top: 100px;
  height: 600px;
  background-color: #f7f7f7;
}

.product {
  display: flex;
  margin-bottom: 5px;
}

.product > span {
  flex-grow: 1;
}

.product > button {
  width: 40px;
  height: 40px;
  background-color: #47caff;
}

.cart {
  position: absolute;
  bottom: 0;
  background-color: #73bc83;
}
</style>

<style>
.ball {
  position: absolute;
  display: none;
  width: 20px;
  height: 20px;
  transition: all 0.4s;
  top: 50%;
  left: 50%;
  margin-left: -10px;
  margin-top: -10px;
  transition-timing-function: cubic-bezier(.49, -3,.75,.41);
  /*可以调节这个 -3 这个数值控制*/
}

.inner {
  display: inline-block;
  width: 100%;
  height: 100%;
  background-color: #a175ff;
  border-radius: 50%;
  transition: all 0.4s;
  transition-timing-function: linear;
}
</style>

结语

想写这篇文章是因为看到了抖音上的一个效果:https://www.douyin.com/video/7243312732806630656

他的实现是基于贝塞尔曲线的效果,本质也是多个运动方向不同运动的叠加。但是我觉得他的方式虽然巧妙,但是很难通过调整参数的方式调出自己想要的运动轨迹。

当然我上面的抛物线动画也是,叠加的动画比较难想象。

相反,沿着 SVG 的轨迹运动,开发人员就会方便多了,我们可以在合适的场景下使用合适的方案。