一、目标
按照项目需求,需要完成如下几部分的功能:
1、长按屏幕时,显示能量条动画(类似环形进度条);
2、当能量条充满时,发射小火箭;
二、实现效果
三、步骤
按照需求分析,上述目标可以拆解成3个功能:
1)用Vue实现屏幕长按事件;
2)实现能量条动画;
3)实现火箭发射动画;
1、先说下捕捉屏幕长按事件,在IOS、Android等平台都有API直接可用,但在H5中则需要自己去实现。当然目前也有很多开源的Vue长按组件,但本需求相对简单,没有必要因为一个小功能,引入一个包含了其它复杂功能的组件。长按事件的核心是要添加2个事件监听:touchstart和touchend,本人封装成了一个独立的js,touch.js代码如下(完整功能代码详见git):
export default class Touch {
constructor(node, continueTime) {
this.node = node;
this.continueTime = continueTime * 1000;
this.touchPoint = {
x: 0,
y: 0
};
this.startTime = undefined;
this.endTime = undefined;
this.timerTag = undefined;
this.progress = undefined;
}
registerTouchStart(param) {
let self = this;
self.node.addEventListener("touchstart", (e) => {
console.log("start touch");
clearInterval(self.timerTag);
if (e.preventDefault) {
e.preventDefault();
}
self.touchPoint = {
x: e.changedTouches[0].pageX,
y: e.changedTouches[0].pageY
};
console.log("start touch point:" + JSON.stringify(self.touchPoint));
self.startTime = new Date().getTime();
self.timerTag = setInterval(() => {
self.endTime = new Date().getTime();
let continueTime = (self.endTime - self.startTime) % self.continueTime;
// console.log("continue time:" + continueTime);
self.progress = (continueTime / self.continueTime).toFixed(2);
if (self.progress == 0) {
self.progress = 1.00;
}
// console.log("current progress:" + self.progress);
param.success(self.progress);
}, 500);
console.log("current timer:" + self.timerTag);
});
}
registerTouchEnd(param) {
let self = this;
self.node.addEventListener("touchend", (e) => {
console.log("end touch");
clearInterval(self.timerTag);
console.log("clear current timer:" + self.timerTag);
if (e.preventDefault) {
e.preventDefault();
}
self.endTime = new Date().getTime();
let deltaX = e.changedTouches[0].pageX - self.touchPoint.x;
let deltaY = e.changedTouches[0].pageY - self.touchPoint.y;
console.log("start touch point:" + JSON.stringify(e.changedTouches[0]));
if (Math.abs(deltaX) > 10 || Math.abs(deltaY) > 100) {
console.log("no long touch action.");
return;
}
console.log("final progress:" + self.progress);
param.success(self.progress == 1);
});
}
}
2、在vue页面加载完时,就要注册长按监听事件,即在vue的mounted挂载方法中调用上述js,代码详见第3步:
3、能量条采用了svg绘制,Rocket.vue页面代码如下:
<template>
<div id="rocket">
<div class="rocket" ref="rocket" v-if="shotRocket">
<img src="assets/rocket.png">
</div>
<div ref="rocket-circle" class="rocket-circle" :data-pct="percent">
<svg
width="100"
height="100"
viewPort="0 0 50 50"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
>
<circle class="circle-inner" :r="radius" cx="50" cy="50" fill="transparent"></circle>
<circle ref="circle-bar" class="circle-bar" :r="radius" cx="50" cy="50" fill="transparent"></circle>
</svg>
</div>
</div>
</template>
<script>
import Touch from "../commons/touch";
export default {
name: "Rocket",
data() {
return {
percent: 0,
radius: 45,
showRocket: false
};
},
mounted: function() {
let self = this;
let node = self.$refs["rocket-circle"];
let touch = new Touch(node, 5);
touch.registerTouchStart({
success: percent => {
console.log("current percent:" + percent);
self.percentChange(percent);
}
});
touch.registerTouchEnd({
success: result => {
console.log("current result:" + result);
if (result) {
self.shotRocket();
}
}
});
},
methods: {
shotRocket: function() {
let self = this;
let rocket = self.$refs["rocket"];
let maxHight = window.screen.availHeight;
let timer = setInterval(() => {
let bottom = parseInt(rocket.style.bottom);
if (!bottom) {
bottom = 15;
}
bottom += 18;
console.log("start move to:" + bottom);
rocket.style.bottom = bottom + "px";
if (bottom >= maxHight) {
clearInterval(timer);
console.log("finish move:" + bottom);
self.showRocket = false;
}
}, 100);
},
percentChange: function(percent) {
let self = this;
let input = this.$refs["percent"];
let cirle = self.$refs["circle-bar"];
let rocket = self.$refs["rocket"];
if (isNaN(percent)) {
percent = 0;
} else {
let r = self.radius;
let c = Math.PI * (r * 2);
if (percent < 0) {
percent = 0;
}
if (percent > 1) {
percent = 1;
}
if (percent > 0 && !self.showRocket) {
self.showRocket = true;
rocket.style.bottom = "15px";
}
let pct = percent * c;
cirle.style.strokeDashoffset = c - pct;
cirle.style.strokeDashArray = pct + " " + (c - pct);
cirle.style.stroke = "#ff9f1e";
self.percent = percent * 100;
}
}
}
};
</script>
<style scoped>
#rocket {
font-family: "Avenir", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
}
.rocket {
height: 100px;
width: 100px;
position: absolute;
z-index: 1000;
bottom: 15px;
left: 50%;
margin-left: -50px;
display: flex;
justify-content: center;
align-items: center;
}
.rocket img {
height: 60px;
width: 60px;
}
svg {
transform: rotate(-90deg);
}
svg circle {
stroke-dashoffset: 0px;
stroke: #666;
stroke-width: 10px;
}
svg .circle-inner {
stroke-dasharray: 282.74px;
stroke: #666;
}
svg .circle-bar {
stroke-dasharray: 282.74px;
transition: stroke-dasharray 0.5s linear;
}
.rocket-circle {
height: 100px;
width: 100px;
box-shadow: 0 0 5px black;
border-radius: 100%;
position: absolute;
z-index: 1000;
bottom: 15px;
left: 50%;
margin-left: -50px;
}
.rocket-circle:after {
height: 80px;
width: 80px;
box-shadow: inset 0 0 5px black;
content: attr(data-pct) "%";
border-radius: 100%;
line-height: 80px;
font-size: 10px;
text-shadow: 0 0 5px black;
margin-top: -40px;
margin-left: -40px;
position: absolute;
z-index: 1000;
bottom: 15px;
left: 50%;
top: 50%;
display: flex;
justify-content: center;
}
.circle-input {
width: 100px;
position: absolute;
z-index: 1000;
bottom: 155px;
left: 50%;
margin-left: -50px;
}
</style>
4、根据上述的touch.js中设置的长按时长,换算成能量环的充值百分比返回给能量环,能量环通过改变svg circle的stroke-dasharray和stroke-offset来实现充值过程的动画(代码详见第3步)。
注意:
1)svg的transform:rotate用来控制circle的起始原点,起始原点默认在水平X轴的圆环上,现在反向旋转90度(逆时针),即圆的起始原点变成了Y轴;
2)stroke-dasharray表示虚线,通过一组数字来表示实现、虚线(空格隔开,先实后虚,且该数组为偶数,如果为奇数自动X2),比如上述代码中就同时设置了实线和虚线;stroke-offset来表示虚线的起始位置;
5、火箭发射动画则比较简单,在能量环充满且长按结束时,就设置一个定时器,不停的修改小火箭的bottom值,当bottom值超过屏幕时,停止定时器;
四、总结
1、vue长按事件相对较容易,在网上找到相关的原理就很好实现;
2、svg绘图理解起来比较痛苦,尤其是刚开始的时候,动画是根据stroke-offset来的,导致圆环绘制不规则,后面改成了按照stroke-dasharray来,且同时设置了stroke-dasharray的实线和虚线部分,动画才规律;后面把圆环动画的起始位置从水平轴改到垂直轴又花了不少时间,主要还是不了解;
3、火箭发射比较简单,但是组合完成这2个动画,足足耗费了我3天时间,希望读者可以少走弯路;
五、参考资料
[1]https://www.jb51.net/article/152307.htm
[2]https://www.cnblogs.com/padding1015/p/9277922.html