利用 svg 的功能我们可以实现文字的不规则排布。
<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
。
<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>
实际上,有了上面那个例子的实现,那么抛物线动画当然是沿用上面的思路。
可是有些场景下,我们并不能提前确定,曲线是什么样的,比如:饿了么的添加购物车的效果。
这个时候,我们使用两个元素将横向和纵向的移动效果叠加起来实现会更方便。
由于我们的商品有很多,商品的列表一般是可以滚动的,所以动画起点是不确定的,动画的终点是确定的。
<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 的轨迹运动,开发人员就会方便多了,我们可以在合适的场景下使用合适的方案。