同学们可以私信我加入学习群!
正文开始
- 前言
- 更新功能所有文章汇总
- 一、两个同心球效果实现
- 二、球内进度条、logo、粒子元素实现
-
- [2.1 球内包含几个元素:](#2.1 球内包含几个元素:)
- [2.2 随机粒子生成方法generateRandomPoint](#2.2 随机粒子生成方法generateRandomPoint)
- [2.3 创建多个粒子的方法createParticle](#2.3 创建多个粒子的方法createParticle)
- 三、gsap创建路径动画,实现粒子动画
- 总结
前言
更新功能的实现分为三篇文章来讲解:
1.主进程更新
2.开发调试技巧
3.渲染进程部分。
我在开发过程中,是先开发的主进程主要功能,再完善渲染进程部分的显示。因为当没有前端时,可以写几个简单的标签显示结果,保证主要功能基本完成,再完善前端交互。
效果图如下:
上面有几个简单的交互:
- 打开软件时,页面向主进程通信,查询是否更新
- 发现需要更新,弹出一个更新交互页面,页面显示更新信息和更新操作
- 更新时,点击关闭按钮,会进入后台更新模式,登录进去后,点击左上角的更新标志,会弹出更新页面。
大的需求就是这样三个,至于细节后文再展开叙述,比如跳过版本如何实现,如何保障登录页和登录后,更新的进度是保持一致的......
更新功能所有文章汇总
- electron-updater实现electron全量更新和增量更新------主进程部分
- electron-updater实现electron全量更新和增量更新------注意事项/技巧汇总
- electron-updater实现electron全量更新和增量更新------渲染进程UI部分
- electron-updater实现electron全量更新和增量更新------渲染进程交互部分
一、两个同心球效果实现
在讲解前,我们先整体了解一下前端的文件结构:
- updateprogress.vue是唯一的vue文件
- updateBall.js:和中间的球相关的逻辑
- updateHandle.js:操作按钮相关的逻辑
- store/update.js:更新模块的全局变量
静态页面部分没什么好说的,就是一个遮罩加两个球,还有一些按钮,我的审美并不好,大家可以自行设计。球的立体效果就是靠阴影效果,代码如下:
c
<div
:style="{ width: ballRadius * 2 + 'px', height: ballRadius * 2 + 'px' }"
class="ball">
</div>
<style scope>
.ball {
border-radius: 50%;
//background: linear-gradient(135deg, #fff, #ddd);
background-color: #fff;
box-shadow: inset 0 0 0 2px rgba(0, 0, 0, .1),
0 0 10px 2px rgba(0, 0, 0, .1);
display: flex;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
flex-direction: column;
}
.ball::before {
content: "";
width: 80%;
height: 80%;
border-radius: 50%;
//background: linear-gradient(315deg, #ddd, #fff);
background-color: #fff;
box-shadow: inset 0 0 0 2px rgba(0, 0, 0, .1),
0 0 10px 2px rgba(0, 0, 0, .1);
position: absolute;
}
</style>
类ball是外层的大球,设置的半径ballRadius 为100,伪类:before是小球,大球小球靠阴影效果,塑造立体感。
二、球内进度条、logo、粒子元素实现
2.1 球内包含几个元素:
- logo
- 不规则的粒子
- 进度条
外层的ball采用的flex布局,布局方向是纵向,所以logo通过图片标签直接引入,设置好宽高即可,下面依次写进度条、粒子等元素:
c
<div :style="{width:ballRadius*2+'px',height:ballRadius*2+'px'}" class="ball">
<img alt="Logo" class="logo" src="/public/img/log-opacity.png">
<div style="display: flex;flex-direction: column;width: 60%">
<Progress :percent="update_info.percent || 0" :stroke-width="5" style="flex-grow: 1;margin-right: 4px">
<div style="width: 100%;display: flex;flex-direction: row;justify-content: center;align-content: center">
<span>{{ update_info.percent || 0 }}%</span>
<span style="color: #1a1a1a;margin-left: 8px">{{ update_info.speed || '0kb' }}</span>
</div>
</Progress>
</div>
<div class="particles">
<!-- 循环渲染粒子-->
<div v-for="(particleItem,index) in particlesList"
:key="index"
:ref="el => setItemRef(index, el)"
:style="{left: particleItem.left+'px',top:particleItem.top+'px',backgroundColor:particleItem.color}"
class="particle">
</div>
</div>
</div>
2.2 随机粒子生成方法generateRandomPoint
重点讲解一下上面的粒子渲染。
首先,粒子的容器是和最大的球重合的,所以采用absolute定位,css代码如下:
c
.particles {
position: absolute;
width: 100%;
height: 100%;
}
粒子渲染最重要的是随机在圆环位置生成粒子的逻辑,这部分的算法核心其实就是初中数学知识:
- 外球半径(outRadius)-内球半径(radius)=圆环半径
- 圆环半径*随机数+内球半径(radius)=落在圆环内的随机半径
- 落在圆环内的随机半径*角度的cos和sin值,就分别是随机半径终点的坐标
转化为代码就是:
c
//生成radius外,outRadius内圆环上的坐标
const randomRadius = radius + Math.random() * (outRadius - radius)
const outX = randomRadius * Math.cos(angle)
const outY = randomRadius * Math.sin(angle)
上面的代码就会生成angle角度方向,不同半径的坐标,如果angle角度也是随机的,那么就会生成圆环内随机散布的粒子
c
const angle = Math.random() * (2 * Math.PI)
const randomRadius = radius + Math.random() * (outRadius - radius)
const outX = randomRadius * Math.cos(angle)
const outY = randomRadius * Math.sin(angle)
然后根据这个算法,我又增加了两种场景,一种是随机粒子是在内球半径上,得到粒子的坐标,思路是一样的,就不再赘述:
c
//生成半径radius上的坐标
const innerX = Math.random() * radius * Math.cos(angle)
const innerY = Math.random() * radius * Math.sin(angle)
另一种是随机粒子是在内球半径内,也就是粒子随机散布在小球内:
c
//生成半径radius内的坐标
const innerX = Math.random() * radius * Math.cos(angle)
const innerY = Math.random() * radius * Math.sin(angle)
最终这个方法就是:
c
function generateRandomPoint(radius, outRadius) {
//根据半径生成随机坐标,radiusArr:半径上的坐标,outRadiusArr:半径外的坐标,innerRadiusArr:半径内的坐标
//outRadiusArr只有存在外环半径outRadius时才会生成
// 生成一个0到2π之间的随机角度
const angle = Math.random() * (2 * Math.PI)
// 极坐标到笛卡尔坐标的转换,生成半径radius上的坐标
const x = radius * Math.cos(angle)
const y = radius * Math.sin(angle)
//生成半径radius内的坐标
const innerX = Math.random() * radius * Math.cos(angle)
const innerY = Math.random() * radius * Math.sin(angle)
//生成radius外,outRadius内的坐标
// debugger
const randomRadius = radius + Math.random() * (outRadius - radius)
const outX = randomRadius * Math.cos(angle)
const outY = randomRadius * Math.sin(angle)
return {
radiusArr: [x, y],
innerRadiusArr: [innerX, innerY],
outRadiusArr: [outX, outY]
}
}
本文的设计是将粒子散布在圆环内,所以只需要返回值的outRadiusArr参数即可。
2.3 创建多个粒子的方法createParticle
通过上面的generateRandomPoint方法,我们可以得到粒子的一个随机坐标。只要我们循环调用该方法,那就会循环得到粒子的不同坐标,代码如下:
c
function createParticle(num) {
// debugger
// 根据num生成随机粒子
for (let i = 0; i < num; i++) {
const {
outRadiusArr: [outX, outY]
} = generateRandomPoint(70, ballRadius.value)
const particle = {
color: getRandomRGBColor(),
left: 100 + outX,
top: 100 + outY,
}
particlesList.value.push(particle)
}
}
其中的getRandomRGBColor方法是获取随机颜色的方法:
c
function getRandomRGBColor() {
// 限制绿色和蓝色分量在100到255之间,红色分量在0到100之间
const r = Math.floor(Math.random() * 101) // 0 to 100
const g = Math.floor(Math.random() * 156) + 100 // 100 to 255
const b = Math.floor(Math.random() * 156) + 100 // 100 to 255
return `rgb(${r}, ${g}, ${b})`
}
最终使用vue的v-for将粒子循环渲染到页面。
三、gsap创建路径动画,实现粒子动画
使用gsap插件,可以很方便地实现元素围绕某个路径运动。不仅限于圆弧,还可以是曲线、折线、不规则图形,gsap是flash基于js上的实现,功能十分强大,有兴趣的同学可以查看往期博文,这里不重点阐述概念。
创建动画的方法如下:
c
function createAnimation(movementRange = 3) {
// 使用GSAP创建动画
particlesList.value.forEach((particle, index) => {
// 使用GSAP创建动画
gsap.to(particleRefs.value[index], {
motionPath: {
path: '#svg',
align: '#svg',
alignOrigin: [Math.random() * 10 - 5, Math.random() * 10 - 5]
},
repeat: -1, // 无限重复
duration: 3 * Math.random() + 2, // 随机持续时间
ease: 'linear', // 线性运动
delay: Math.random() * 2 // 随机延迟
})
})
}
这里有几个小技巧:
- 仔细的同学可以发现,在vue中,粒子的ref变量是通过setItemRef方法实现的
c
//vue中的代码
<div v-for="(particleItem,index) in particlesList"
:key="index"
:ref="el => setItemRef(index, el)"
:style="{left: particleItem.left+'px',top:particleItem.top+'px',backgroundColor:particleItem.color}"
class="particle">
</div>
//对应的js代码
function setItemRef(index, el) {
if (el) {
// 如果元素存在,则将其存储在对象中
particleRefs.value[index] = el
} else {
// 如果元素不存在(可能是被销毁了),则从对象中删除
delete particleRefs.value[index]
}
}
这个方法最终会得到一个保存粒子ref对象的数组particleRefs。
- gsap动画需要指定动画元素, gsap.to的第一个参数particleRefs.value[index]就是循环得到的动画元素,也就是每一个粒子。
- gsap的基础配置十分简单,这里主要是讲解motionPath参数。这是路径插件的参数:
c
motionPath: {
path: '#svg',
align: '#svg',
alignOrigin: [Math.random() * 10 - 5, Math.random() * 10 - 5]
},
前面两个参数好理解,就是粒子绕着路径运动,总得先定义路径,svg就是路径的id。alignOrigin是粒子偏移路径的距离,使用随机数,可以让粒子运动效果有杂乱随机的感觉。
- svg是路径path的id,不是svg标签的id,这个要注意,对应的svg代码如下:
c
<!-- SVG 圆形元素 -->
<svg style="position: absolute" height="95%" viewBox="-160 -160 320 320" width="95%"
xmlns="http://www.w3.org/2000/svg">
<path id="svg"
d="M 0 160
A 160 160 0 0 1 0 -160
A 160 160 0 0 1 0 160 Z"
fill="transparent" stroke="none"/>
</svg>
构建svg的时候,还要注意原点、是否闭合、起止点等信息,也就是d元素中的数据。如果d属性的路径设置不合理,可能会造成path路径与外部的圆不重合的问题。当然这些一般没人去手输,通过Adobe AI软件、在线svg绘制网站、ai助手等,都可以得到符合要求的svg路径。
对svg不熟悉的同学,要关注viewBox属性,这是svg可以跟随父级容器按照比例增大缩小的关键。
- 启动动画:当用户点击立即更新时,需要做三件事:1)创建200个粒子;2)启动动画;3)检查更新。代码如下:
c
function startUpdate() {
createParticle(200)
setTimeout(() => {
createAnimation()
myApi.handlePcToUpdate()
}, 100)
}
总结
本文主要是讲解了更新模块的页面样式实现,下一篇文章讲解页面上的交互逻辑实现。
大家如果需要联系博主,或者获取博主各系列文章对应的资源,可以通过中二少年学编程的个人主页来获取。
有任何前端项目、demo、教程需求,都可以联系博主,博主会视精力更新,免费的羊毛,不薅白不薅!~