我们一般会引入anime.js来改变svg动画,但是anime.js源码有一千多行,但我们只需要修改svg这部分的时候,可以通过我们自己手写一段代码来实现svg动画,来优化性能。
首先来看先anime.js是如何改变svg动画:
1、在script标签中引入anime.js:<script src="anime.js"></script>;
2、svg中有path属性,通过改变path中的d、fill、stroke来改变动画效果。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <svg class="social" xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"> <path class="path" style="fill:#3b5998;fill-rule:evenodd;stroke:#3b5998;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="m 41.416254,90 c -0.327378,-7.4702 0.20833,-32.7284 0,-39.901 -5.386902,-0.2083 -4.521603,0.3274 -9.848987,0 0.20833,-5.50595 0.36436,-7.66666 0.126269,-13.32142 4.646472,0.0181 3.439989,-0.009 9.848987,-0.1894 0.09586,-3.7736 0.133082,-3.0791 0.126269,-7.38674 0.18259,-3.73943 -0.486609,-10.54308 4.293149,-14.96288 4.779758,-4.4198 13.606811,-3.64808 22.223356,-3.53554 -0.04417,5.73754 -0.03936,9.37986 0,12.87945 -5.049924,0.46388 -7.309188,-0.33689 -10.85914,1.26269 -1.403378,3.17794 -1.569601,4.80531 -1.262691,11.93242 3.147964,-0.13336 8.201788,-0.1378 12.626907,0 -0.995158,6.00899 -0.948285,7.62376 -1.767767,13.06882 -3.676625,0.088 -5.605721,-0.1488 -11.111678,0 -0.148814,6.756 0.357147,33.0107 0,40.1536 -6.428576,0.1786 -8.174438,-0.03 -14.394674,0 z" /> </svg> <script src="anime.js"></script> <script> anime({ targets: '.path', d: { // 改变d,从一种状态改变到另一种状态 value: [ "m 41.416254,90 c -0.327378,-7.4702 0.20833,-32.7284 0,-39.901 -5.386902,-0.2083 -4.521603,0.3274 -9.848987,0 0.20833,-5.50595 0.36436,-7.66666 0.126269,-13.32142 4.646472,0.0181 3.439989,-0.009 9.848987,-0.1894 0.09586,-3.7736 0.133082,-3.0791 0.126269,-7.38674 0.18259,-3.73943 -0.486609,-10.54308 4.293149,-14.96288 4.779758,-4.4198 13.606811,-3.64808 22.223356,-3.53554 -0.04417,5.73754 -0.03936,9.37986 0,12.87945 -5.049924,0.46388 -7.309188,-0.33689 -10.85914,1.26269 -1.403378,3.17794 -1.569601,4.80531 -1.262691,11.93242 3.147964,-0.13336 8.201788,-0.1378 12.626907,0 -0.995158,6.00899 -0.948285,7.62376 -1.767767,13.06882 -3.676625,0.088 -5.605721,-0.1488 -11.111678,0 -0.148814,6.756 0.357147,33.0107 0,40.1536 -6.428576,0.1786 -8.174438,-0.03 -14.394674,0 z", "m 10.44335,90 c 11.073313,0.3952 19.483106,-1.8358 23.901837,-7.1603 -7.9736,-1.4292 -11.832311,-4.1933 -15.078321,-11.0837 3.459698,0.8219 5.795894,0.6358 7.606781,-0.607 -7.19593,-1.719 -12.734543,-6.7971 -13.741664,-15.836 2.766355,1.55307 5.466848,2.66623 7.828682,2.0203 -4.336544,-2.92911 -9.838998,-10.47636 -5.555839,-22.47589 8.400675,11.87052 23.824269,17.67568 33.840111,17.67767 -0.936406,-9.74688 5.88057,-19.46521 15.302849,-19.97853 8.13118,-0.50719 10.57457,4.01944 12.476346,4.82624 3.644547,0.13419 7.393301,-1.74401 10.354063,-3.53553 -1.380842,4.47157 -5.06769,5.62903 -6.313453,8.58629 5.42317,0.41513 5.891376,-1.53111 8.333758,-2.0203 -2.071414,3.75017 -5.393863,5.00034 -7.323606,8.08122 -1.633654,16.12573 -5.16049,27.57123 -14.647212,36.36553 -13.825764,11.3764 -34.755458,17.369 -56.984332,5.14 z" ], // 动画持续时间 duration: 2000, // 动画缓动效果 easing: 'easeInOutQuart' }, // 填充的颜色 fill: { value: ['#3b5998', '#4099ff'], duration: 2000, easing: 'easeInOutQuart' }, // stroke是线的颜色 stroke: { value: ['#3b5998', '#4099ff'], duration: 2000, easing: 'easeInOutQuart' }, }) </script> </body> </html>
我们创建new-anime.js文件,来实现animejs中实现svg的部分,细节详见代码的备注部分:
function anime(params) {
// instance:相关属性仍然挂载在一个对象下,代表当前动画
// raf ;代表当前标识
var instance = {},raf;
instance.duration = 0;
// 1. 将动画需要改变的属性存储起来
// properties中存储d、fill、stroke这三个属性
instance.properties = [];
instance.animations = [];
for (var key in params) {
if (key !== 'targets') {
instance.properties.push(key)
} else {
instance[key] = params[key];
}
}
// 2. 将属性和目标元素匹配,生成动画数据
instance.properties.forEach(function (item) {
// animation:每拿到一个属性,就会创建一个动画数据,用animation来接收
var animation = {};
animation.property = item;
// from:是d、fill、stroke中value的第一个值 、fill中value针对颜色我们还要进行转换
// 如果是颜色值,我们还需要对颜色进行转换成rgb,然后再存储起来
// to:是d、fill、stroke中value的第二个值
// isHex:判断是否为颜色值
// hexToRgba:转换为rgb的格式
animation.from = isHex(params[item].value[0]) ? hexToRgba(params[item].value[0]) : params[item].value[0];
animation.to = isHex(params[item].value[1]) ? hexToRgba(params[item].value[1]) : params[item].value[1];
// 将每个值单独取出来,进行处理.
// 比如将rgb中的r取出来r跟r计算 / g跟g计算...
animation.from = valueNormalize(animation.from);
animation.to = valueNormalize(animation.to);
// 存储属性
animation.duration = params[item].duration;
animation.easing = params[item].easing;
// 将持续时间取最大值赋值给
instance.duration = instance.duration > animation.duration ? instance.duration : animation.duration;
instance.animations.push(animation);
})
// 处理每一帧
instance.tick = function (t) {
if (t > instance.duration) {
return;
}
instance.animations.forEach(function (animation) {
if (t <= animation.duration) {
var value = [];
// animation.from.numbers:这里就是把属性的r拆开了,把d中的数字都拆开了
animation.from.numbers.forEach(function (item, index) {
// 计算改变量:颜色值是不允许出现小数的,但是d的点的坐标是可以的,所以要做区分
if (isRgb(animation.from.original)) {
// easeInQuart:运动的改变量
// 将其转为整数,颜色的值不可以带小数,然后存储到value中
value.push(parseInt(easeInQuart(t,animation.from.numbers[index],animation.to.numbers[index] - animation.from.numbers[index],animation.duration)));
} else {
value.push(easeInQuart(t,animation.from.numbers[index],animation.to.numbers[index] - animation.from.numbers[index],animation.duration));
}
})
}
var strings = animation.from.strings;
var stringsLength = strings.length;
var progress = strings[0];
for (var s = 0; s < stringsLength; s++) {
var b = strings[s + 1];
var n$1 = value[s];
if (!isNaN(n$1)) {
if (!b) {
progress += n$1 + ' ';
} else {
progress += n$1 + b;
}
}
}
// 设置属性
// d是作为path的属性的
// fill和stock是style下的
if (isRgb(progress)) {
instance.targets.style[animation.property] = progress;
} else {
instance.targets.setAttribute([animation.property], progress);
}
})
}
// 动画引擎:
if(!raf){
play();
}
function play () {
raf = requestAnimationFrame(step);
}
// 处理每一帧
function step(t) {
instance.tick(t);
if(t > instance.duration){
return;
}
play()
}
}
// 匀速运动的改变:(当前的时间t / 当前的持续时间) * (from - to的时间差)
// 但是由于 easing: 'easeInOutQuart',这是一个缓动的算法,我们使用前人总结的如下算法来计算运动的改变:
// t:当前时间、b:起始值、c:改变量(也就是终止值和起始值的差)、d:总的持续时间
function easeInQuart (t, b, c, d) {
return c * (t /= d) * t * t * t + b;
}
// hexToRgba:对颜色进行rgb的转换
function hexToRgba(hexValue) {
var rgx = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
var hex = hexValue.replace(rgx, function (m, r, g, b) { return r + r + g + g + b + b; });
var rgb = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
var r = parseInt(rgb[1], 16);
var g = parseInt(rgb[2], 16);
var b = parseInt(rgb[3], 16);
return ("rgba(" + r + "," + g + "," + b + ",1)");
}
// isHex:判断是否为颜色值
function isHex(a) { return /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(a); }
// isRgb:判断是否为rgb
function isRgb(a) { return /^rgb/.test(a); }
// 把每个值单独取出来
function valueNormalize(val) {
var rgx = /[+-]?\d*\.?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/g; // handles exponents notation
return {
original: val,
numbers: val.match(rgx) ? val.match(rgx).map(Number) : [0],
// 用来存单位
strings: val.length ? val.split(rgx) : []
}
}
创建完成,这时我们同样通过script标签引入我们自己实现的包
<script src="new-anime.js"></script>
由于没有进行targets的处理,这里需要使用原生js方法来获取目标元素,除了这一点与animejs不同,其余效果我们可以看到完全相同,并且只书写了一百多行代码,性能得到了提升:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<svg class="social" xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100">
<path class="path"
style="fill:#3b5998;fill-rule:evenodd;stroke:#3b5998;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 41.416254,90 c -0.327378,-7.4702 0.20833,-32.7284 0,-39.901 -5.386902,-0.2083 -4.521603,0.3274 -9.848987,0 0.20833,-5.50595 0.36436,-7.66666 0.126269,-13.32142 4.646472,0.0181 3.439989,-0.009 9.848987,-0.1894 0.09586,-3.7736 0.133082,-3.0791 0.126269,-7.38674 0.18259,-3.73943 -0.486609,-10.54308 4.293149,-14.96288 4.779758,-4.4198 13.606811,-3.64808 22.223356,-3.53554 -0.04417,5.73754 -0.03936,9.37986 0,12.87945 -5.049924,0.46388 -7.309188,-0.33689 -10.85914,1.26269 -1.403378,3.17794 -1.569601,4.80531 -1.262691,11.93242 3.147964,-0.13336 8.201788,-0.1378 12.626907,0 -0.995158,6.00899 -0.948285,7.62376 -1.767767,13.06882 -3.676625,0.088 -5.605721,-0.1488 -11.111678,0 -0.148814,6.756 0.357147,33.0107 0,40.1536 -6.428576,0.1786 -8.174438,-0.03 -14.394674,0 z" />
</svg>
<script src="new-anime.js"></script>
<script>
anime({
targets: document.querySelector('path'),
d: {
value: [
"m 41.416254,90 c -0.327378,-7.4702 0.20833,-32.7284 0,-39.901 -5.386902,-0.2083 -4.521603,0.3274 -9.848987,0 0.20833,-5.50595 0.36436,-7.66666 0.126269,-13.32142 4.646472,0.0181 3.439989,-0.009 9.848987,-0.1894 0.09586,-3.7736 0.133082,-3.0791 0.126269,-7.38674 0.18259,-3.73943 -0.486609,-10.54308 4.293149,-14.96288 4.779758,-4.4198 13.606811,-3.64808 22.223356,-3.53554 -0.04417,5.73754 -0.03936,9.37986 0,12.87945 -5.049924,0.46388 -7.309188,-0.33689 -10.85914,1.26269 -1.403378,3.17794 -1.569601,4.80531 -1.262691,11.93242 3.147964,-0.13336 8.201788,-0.1378 12.626907,0 -0.995158,6.00899 -0.948285,7.62376 -1.767767,13.06882 -3.676625,0.088 -5.605721,-0.1488 -11.111678,0 -0.148814,6.756 0.357147,33.0107 0,40.1536 -6.428576,0.1786 -8.174438,-0.03 -14.394674,0 z",
"m 10.44335,90 c 11.073313,0.3952 19.483106,-1.8358 23.901837,-7.1603 -7.9736,-1.4292 -11.832311,-4.1933 -15.078321,-11.0837 3.459698,0.8219 5.795894,0.6358 7.606781,-0.607 -7.19593,-1.719 -12.734543,-6.7971 -13.741664,-15.836 2.766355,1.55307 5.466848,2.66623 7.828682,2.0203 -4.336544,-2.92911 -9.838998,-10.47636 -5.555839,-22.47589 8.400675,11.87052 23.824269,17.67568 33.840111,17.67767 -0.936406,-9.74688 5.88057,-19.46521 15.302849,-19.97853 8.13118,-0.50719 10.57457,4.01944 12.476346,4.82624 3.644547,0.13419 7.393301,-1.74401 10.354063,-3.53553 -1.380842,4.47157 -5.06769,5.62903 -6.313453,8.58629 5.42317,0.41513 5.891376,-1.53111 8.333758,-2.0203 -2.071414,3.75017 -5.393863,5.00034 -7.323606,8.08122 -1.633654,16.12573 -5.16049,27.57123 -14.647212,36.36553 -13.825764,11.3764 -34.755458,17.369 -56.984332,5.14 z"
],
duration: 2000,
easing: 'easeInOutQuart'
},
fill: {
value: ['#3b5998', '#4099ff'],
duration: 2000,
easing: 'easeInOutQuart'
},
stroke: {
value: ['#3b5998', '#4099ff'],
duration: 2000,
easing: 'easeInOutQuart'
},
})
</script>
</body>
</html>
这里我再进行一下总结,实现svg的内容主要分为一下4个步骤:
1. 处理参数属性
2. 将参数属性处理成动画数据
3. 设置帧
4. 动画引擎