<script id="fragmentShader" type="x-shader/x-fragment">
precision mediump float;
uniform vec4 u_FragColor;
void main() {
float dist = distance(gl_PointCoord, vec2(0.5, 0.5));
if(dist < 0.5) {
gl_FragColor = u_FragColor;
} else {
discard;
}
}
script>
#canvas {
background: url("./bg.jpg");
background-size: cover;
background-position: right bottom;
}
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.enable(gl.BLEND); // 开启片元的颜色合成功能
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); // 设置片元的合成方式
const arr = new Float32Array([0.87, 0.91, 1, a]);
gl.uniform4fv(u_FragColor, arr);
1 )框架层面的设计
export default class Compose {
constructor() {
this.parent = null // 当前对象parent置空,这是一个编程习惯
this.children = [] // 用于存储多个对象
}
add(obj) {
obj.parent = this // 当前对象和当前工具建立关系
this.children.push(obj) // 将当前对象加入子队列数组
}
// 这里是工具的update
update(t) {
// 内部调用每个对象的update方法实现动画
this.children.forEach(ele => {
ele.update(t)
})
}
}
2 )时间轨的设计
export default class Track {
constructor(target) {
this.target = target // 当前对象
this.parent = null // 父对象,合成对象,默认为空
this.start = 0 // 开始时间默认是0
this.timeLen = 5 // 一个时间轨的长度,也就是完成一次完整动画所需要的时间,单位毫秒
this.loop = false // 是否循环播放动画
this.keyMap = new Map() // 关键帧的集合
}
// 当前对象的每一帧运动函数
update(t) {
const { keyMap, timeLen, target, loop } = this
let time = t - this.start // 当前时间距离开始时间的时间长度
// 如果开启循环,则加入取余操作,将当前时间循环递增,不让超过自身设定
if(loop) {
time = time % timeLen
}
for(const [key,fms] of keyMap.entries()) {
const last = fms.length - 1 // 最后一项
if(time < fms[0][0]) {
// 在第一个关键帧之前的设置为第一个关键帧的状态
target[key] = fms[0][1]
} else if(time > fms[last][0]) {
// 时间在最后一个关键帧之后设定为最后一个关键帧的状态
target[key] = fms[last][1]
} else {
// 在各个中间态实行数学计算状态渐变,即:补间状态
target[key] = getValBetweenFms(time, fms, last)
}
}
}
}
// 补间状态计算函数
function getValBetweenFms(time,fms,last) {
for(let i = 0; i < last; i++) {
const fm1 = fms[i]
const fm2 = fms[i+1]
if(time >= fm1[0] && time <= fm2[0]) {
const delta = {
x: fm2[0] - fm1[0],
y: fm2[1] - fm1[1],
}
const k = delta.y / delta.x
const b = fm1[1] - fm1[0] * k
return k * time + b
}
}
}
[
[
'对象属性1',
[
[时间1,属性值], //关键帧
[时间2,属性值], //关键帧
]
],
[
'对象属性2',
[
[时间1,属性值], //关键帧
[时间2,属性值], //关键帧
]
],
]
3 )应用
const compose = new Compose()
const stars = [] // 点数据的集合
canvas.addEventListener('click', function(event) {
const { x, y } = getPosByMouse(event,canvas) // 获取当前坐标,这里具体实现可看之前博客代码,只是做了个函数封装
const a = 1
const s = Math.random() * 5 + 2
const obj = { x, y, s, a } // x坐标,y坐标,s尺寸,a透明度
stars.push(obj)
const track = new Track(obj)
track.start = new Date()
track.keyMap = new Map([
['a', [
[500, a],
[1000, 0],
[1500, a],
]]
])
track.timeLen = 2000
track.loop = true
compose.add(track)
})
渲染方法如下,参考之前博客代码
function render(){
gl.clear(gl.COLOR_BUFFER_BIT);
stars.forEach(({x,y,s,a}) => {
gl.vertexAttrib2f(a_Position,x,y);
gl.vertexAttrib1f(a_PointSize,s);
gl.uniform4fv(u_FragColor, new Float32Array([0.87,0.92,1, a]));
gl.drawArrays(gl.POINTS, 0, 1);
})
}
用请求动画帧驱动动画,连续更新数据,渲染视图
!(function ani() {
compose.update(new Date())
render()
requestAnimationFrame(ani) // 重复执行
})()