在uni-App(Vue)中使用 SVG + JS 自定义动画:模拟关键帧

问题

为什么要使用SVG来制作动画?

SVG是矢量图形,放大不会失真,而且动画看起来很丝滑,能够实现很多平面动画效果,这点是CSS动画比不上的

在Vue中使用SVG有哪些难点?

  • 注意载入顺序,应该在mounted() 过程中载入动画,并且记得 在destroyed() 过程之后销毁。
  • 注意SVG内的锚点定位问题,将你所需的图形放在合适的位置上,并保证屏幕尺寸改变时不会影响到内部的位置。

示例

在此案例中,我需要制作一个Loading 图标,并在不同的页面中显示,当网络发起请求后,loading 变量会控制此图标的显示与隐藏,为此,我制作了一个可复用的组件来实现此目的:

上传的gif因压缩原因看起来比较卡顿,实际上是非常丝滑的
请添加图片描述
代码中有我自创的实现关键帧的方式,每隔10ms 计算一次svg内图形的各项属性,并赋值,10ms一帧已经超过动画视觉标准了,如果想减轻设备负载,可以适当调高一点,如果想榨干设备性能,就调快一点。

<template>
	<view>
		<svg height="100%" width="100%" viewBox="-20.654044750430295 -21.996557659208264 1241.3080895008607 1243.9931153184166" x="0" y="0" id="document" class="disable-on-play" fill="transparent">
		    <g id="elements" style="isolation: isolate;">
		        <defs id="dynamic-definitions">
		            <linearGradient id="ellipse-q79an5euegn--stroke" x1="0" y1="0.5" x2="1" y2="0.5" spreadMethod="pad" gradientUnits="objectBoundingBox">
		                <stop id="ellipse-q79an5euegn--stroke-stop-0" offset="0" stop-color="rgb(0,163,255)"></stop>
		                <stop id="ellipse-q79an5euegn--stroke-stop-1" offset="1" stop-color="rgb(0,52,232)"></stop>
		            </linearGradient>
		            <linearGradient id="ellipse-51gfnuexh6c--fill" x1="0" y1="0.5" x2="1" y2="0.5" spreadMethod="pad" gradientUnits="objectBoundingBox">
		                <stop id="ellipse-51gfnuexh6c--fill-stop-0" offset="0" stop-color="rgb(0,163,255)"></stop>
		                <stop id="ellipse-51gfnuexh6c--fill-stop-1" offset="1" stop-color="rgb(0,52,232)"></stop>
		            </linearGradient>
		            <linearGradient id="ellipse-th8rfzjaes--fill" x1="0" y1="0.5" x2="1" y2="0.5" spreadMethod="pad" gradientUnits="objectBoundingBox">
		                <stop id="ellipse-th8rfzjaes--fill-stop-0" offset="0" stop-color="rgb(0,163,255)"></stop>
		                <stop id="ellipse-th8rfzjaes--fill-stop-1" offset="1" stop-color="rgb(0,52,232)"></stop>
		            </linearGradient>
		        </defs>
		        <defs id="definitions"></defs>
		        <g id="elements-wrapper" width="1200" height="1200">
		            <ellipse ref="outter" id="ellipse-q79an5euegn" rx="500" ry="500" transform="matrix(0 -1 1 0 600 600)" 
					fill="none" stroke="url(#ellipse-q79an5euegn--stroke)" stroke-width="100" 
					stroke-linecap="round" :stroke-dasharray="d1 + ' ' + d2" 
					style="mix-blend-mode: normal; paint-order: fill;" 
					:stroke-dashoffset="strokeDashoffset"></ellipse>
		            <ellipse ref="inner" id="ellipse-51gfnuexh6c" rx="928.571429" ry="928.571429" 
					:transform="'matrix(' + sH + ' 0 0 ' + sW + ' 600 600)'" fill="url(#ellipse-51gfnuexh6c--fill)" 
					stroke="none" stroke-width="15" style="mix-blend-mode: normal; paint-order: fill;"></ellipse>
		        </g>
		    </g>
		    <path d="M-20.654044750430295 -21.996557659208264 h1241.3080895008607 v1243.9931153184166 h-1241.3080895008607 z M0 0 h1200 v1200 h-1200 z" fill-rule="evenodd" id="overlay"></path>    <!---->    <!---->
		    <g id="tool-selection">
		        <g></g>
		        <g>            <!---->            <!----></g>        <!----></g>
		</svg>
	</view>
</template>

<script>
	export default {
		name:"ts-loading",
		data() {
			return {
				d1:0,
				d2:3600,
				strokeDashoffset:0,
				sH:0,
				sW:0,
				timer:null,
			};
		},
		computed:{
			
		},
		mounted() {
			this.addInterval()
		},
		onShow() {
			
		},
		destroyed() {
			clearInterval(this.timer)
			this.timer = null
		},
		methods:{
			addInterval(){
				const that = this
				var t = 0; // 时间
				var et = 2000; //持续时间
				var keyframes = [0,1000,2000]
				var scaleFrames = [0,0.35,0]
				var stockeOffestFrames = [0,0,-3600]
				var stockDashes1Frames = [0,3600,3600]
				var stockDashes2Frames = [3600,3600,3600]
				var targetScale = 0;
				var targetstockeOffest = 0;
				var targetstockDashes1 = 0;
				var targetstockDashes2 = 3600;
				this.timer = setInterval(() =>{
				for (var i = 0; i < keyframes.length; i++) {
					if(t === keyframes[i]){
						targetScale = scaleFrames[i + 1]
						targetstockeOffest = stockeOffestFrames[i + 1]
						targetstockDashes1 = stockDashes1Frames[i + 1]
						targetstockDashes2 = stockDashes2Frames[i + 1]
						break
					}
				}
				that.d1  = that.d1  + (targetstockDashes1 - that.d1 ) / 10
				that.d2 = that.d2 + (targetstockDashes2 - that.d2) / 10
				that.strokeDashoffset = that.strokeDashoffset + (targetstockeOffest - that.strokeDashoffset) / 10
				that.sH = that.sH + (targetScale - that.sH) / 10
				that.sW = that.sW + (targetScale - that.sW) / 10
				t += 10
				if(t >= et){
					t = 0;
					that.d1 = 0
					that.d2 = 3600
					that.strokeDashoffset = 0
					that.sH = 0
					that.sW = 0
					
				}
			},10)
			}
		}
	}
</script>

<style>
	.outter{
		stroke-width: 15;
		transform-origin: center;
		transform: rotateZ(-90deg);
		stroke: linear-gradient(to right,#00a3ff,#0034e8) 1 10;
	}
	.inner{
		transform-origin: center;
		background-color:  linear-gradient(to right,#00a3ff,#0034e8) 1 10;
	}
</style>

调用方式:

<view style="text-align: center;width: 100%" v-show="loading">
	<ts-loading style="margin: auto;" class="main-loading" ></ts-loading>
</view>
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值