这两天大家的朋友圈应该都被日环食刷屏了吧,有幸亲眼目睹了日环食的小伙伴一定被这十年才能一见的天文奇观给震撼了一番,我作为一个业余天文爱好者,业余到连一个望远镜都没有,只能在日环食这天仰望天空,却被强烈的阳光刺痛了双眼,毛也看不到。
作为一个有追求的前端码农,怎么能在这么有意义的日子里什么也不做呢,于是我灵机一动,干脆手撸一个日环食效果吧。
撸页面之前,我们先脑补一下页面要实现的效果,碧蓝的天空里悬挂着一轮孤独的烈日,突然,她浑圆的身体开始出现黑色的残缺,随着时间的推移,她的身体逐渐被黑色吞噬,苍茫的天空也随之笼罩下压抑的阴霾,天地之间,在一片混沌的漆黑之中,一轮诡异的金色圆环出现了,啊,打住,跑题了。
如图,页面元素比较简单:
- 蓝天
- 太阳
- 月亮
因为要做动画效果,这三个元素都用绝对定位,其中蓝天的宽高都设置为100%就好,太阳元素要设置一个投影滤镜(作为发光效果)。需要注意的是:月亮要对太阳进行遮挡,并且显示为黑色,但超出太阳的部分是不可见的,因此在层次结构上,月亮div属于太阳div的子元素,然后太阳div要设置overflow为hidden,这点需要注意。另外,由于是日环食,月亮div的宽高要略小于太阳,具体数值根据效果微调即可。
以下css代码仅供参考
.sky {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #7ad8fb;
}
.sun {
position: absolute;
top: 200px;
right: 200px;
width: 204px;
height: 204px;
border-radius: 200px;
background: #fcf6dc;
overflow: hidden;
box-shadow: 0 0 60px rgba($color: #ffffff, $alpha: 0.6);
}
.moon {
position: absolute;
top: 7px;
left: 7px;
width: 190px;
height: 190px;
border-radius: 190px;
background: #000000;
}
随着日食的推进,天空会逐渐变暗直至黑色,因此还需要一个元素,用来控制天空的明暗程度,这个元素我们就叫它mask吧,它也是绝对定位,并且宽高跟天空一样是铺满全屏的,在层次上,它应该处于天空上方,太阳下方。这个div我们设置它的背景色为黑色,但透明度默认是0,后续用脚本控制透明度来达到天空逐渐变暗的效果。
css代码如下:
.mask {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba($color: #000000, $alpha: 0.9);
opacity: 0;
}
页面的html结构非常简单,如下:
<div class="sky">
<div class="mask"></div>
<div class="sun">
<div class="moon"></div>
</div>
</div>
通过上面的代码,我们已经实现了一个静态的日环食效果,接下来我们就来编写js脚本,控制月亮的移动以及天空的明暗变化。
我用的是vue,但这个不重要,我们只需要关注实现原理即可。实现原理其实也很简单,我们只需要控制好月球的坐标走向即可,为了尽可能实现逼真的效果,我们让月球从太阳的左下角逐渐走到太阳的右上角直至消失,当走到中间的一瞬间,月球和太阳的圆心会重合,由于月球的半径比太阳小,因此正好会出现日环食的效果,注意,当月球圆心和太阳圆心重合的一瞬间,mask遮罩层的透明度应该正好是1,也就是天空完全变黑的一个效果。
大致的原理就是这样,以下是这个页面需要用到的变量:
opacity_step: 0.001, // 透明度的增量(每一次渲染增加的透明度)
sun_width: 0, // 太阳宽度
sun_height: 0, // 太阳高度
moon_width: 0, // 月亮宽度
sun: null, // 太阳dom对象
moon: null, // 月亮dom对象
mask: null, // 遮罩层dom对象
distanceX: 0, // 月球到太阳中心重合点的横向距离
distanceY: 0 // 月球到太阳中心重合点的纵向距离
在页面加载完毕后,我们先对这些变量进行初始化,并且让月球处于左下角的位置,我用的是vue,所以将这部分代码写在mounted函数里:
this.sun = document.querySelector('.sun')
this.moon = document.querySelector('.moon')
this.mask = document.querySelector('.mask')
this.sun_width = this.sun.clientWidth
this.sun_height = this.sun.clientHeight
this.moon_width = this.moon.clientWidth
this.moon.style.left = (this.moon_width * -1) + 'px'
this.moon.style.top = this.sun_height + 'px'
const offsetSize = (this.sun_width - this.moon_width) * 0.5
this.distanceX = this.moon_width + offsetSize
this.distanceY = this.moon_width + offsetSize
this.render()
注意月球初始位置的计算规则,left应该是负的月球的宽度,top应该是太阳的高度。初始化的最后一行代码,调用了render方法,我们将在这个方法里不断更新月球的位置和遮罩层的透明度,为了实现这个不断刷新,可以使用setInterval,但这里我用的是window.requestAnimationFrame,也推荐大家用这个,此方法在MDN的说明如下:
window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行
详见:window.requestAnimationFrame
我们在render方法中引入这个requestAnimationFrame方法
render () {
window.requestAnimationFrame(() => {
// 更新dom元素 todo...
this.render() // 再次调用自己
}
)
通过requestAnimationFrame方法,我们就得到了一个屏幕不断刷新的回调函数,接下来我们只需要告诉浏览器,每次刷新,dom元素的位置或透明度如何变化即可。
首先我们来分析遮罩层mask,根据上文描述,它的透明度需要从0变为1(月球和太阳的圆心重合),然后再从1变为0(月球离开太阳),因此我们只需要在每次渲染时让它的透明度逐渐递增就行,参考代码如下:
let opacity = Number(this.mask.style.opacity) // 获取当前透明度
if (opacity >= 1 || opacity < 0) { // 如果透明度已经大于1 或者小于0
this.opacity_step *= -1 // 就让增量值反转
}
opacity += this.opacity_step
this.mask.style.opacity = opacity // 更新遮罩层的透明度
紧接着我们来分析月球的位置,我们的目标是让月球移动到左上角,说明每次移动,月球的横向偏移量和纵向偏移量都是一样的,因此月球就会沿着一个45°角的轨迹来移动。但这里要注意一个问题,就是月球每次移动多少偏移量,才能在遮罩层正好为1(天空完全变暗)的时候,移动到太阳的正中间?我们在初始化的时候,已经计算好了月球距离太阳圆心的横向距离distanceX和纵向距离distanceY,而遮罩层的透明度增量也是知道的,就是变量opacity_step(0.001),因此可以得出:月球每次偏移量 = 月球到太阳圆心的距离 * opacity_step,然后,当月球已经离开太阳时,让所有变量复原,日食重新开始。
月球的位移计算代码如下:
let left = Number(this.moon.style.left.replace('px', ''))
let top = Number(this.moon.style.top.replace('px', '')) //先获取月球当前的left和top
left += this.distanceX * Math.abs(this.opacity_step)
top -= this.distanceY * Math.abs(this.opacity_step) // 计算月球的位置
this.moon.style.left = left + 'px'
this.moon.style.top = top + 'px' // 更新月球dom元素的位移
if (left > this.sun_width) { // 如果月球已超出太阳的边界,所有变量复原
this.opacity_step = Math.abs(this.opacity_step)
this.mask.style.opacity = 0
this.moon.style.left = (this.moon_width * -1) + 'px'
this.moon.style.top = this.sun_height + 'px'
}
好了,所有的代码都写完了,运行你的页面,你就会看到一个还不错的日环食效果。
完整效果请访问:h5日环食效果
喜欢这篇文章的小伙伴,记得转发、点赞哦,原创不易,请大家多多支持。