这两天大家的朋友圈应该都被日环食刷屏了吧,有幸亲眼目睹了日环食的小伙伴肯定被这十年才能一见的天文奇观给震撼了一番,我作为一个业余天文爱好者,业余到连一个望远镜都没有,只能在日环食这天仰望天空,却被强烈的阳光刺痛了双眼,毛也看不到。
作为一个有追求的前台码农,怎样能在这么有意义的日子里什么也不做呢,于是我灵机一动,干脆手撸一个日环食效果吧。
撸页面之前,我们先脑补一下页面要实现的效果,碧蓝的天空里悬挂着一轮孤独的烈日,忽然,她浑圆的身体开始出现黑色的残缺,随着时间的推移,她的身体逐步被黑色吞噬,苍茫的天空也随之笼罩下压抑的阴霾,天地之间,在一片混沌的漆黑之中,一轮诡异的金色圆环出现了,啊,打住,跑题了。
如图,页面元素比较简单:
由于要做动画效果,这三个元素都用绝对定位,其中蓝天的宽高都设置为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.clientWidththis.sun_height = this.sun.clientHeightthis.moon_width = this.moon.clientWidththis.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.5this.distanceX = this.moon_width + offsetSizethis.distanceY = this.moon_width + offsetSizethis.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_stepthis.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和topleft += 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日环食效果
喜欢这篇文章的小伙伴,记得转发、点赞哦,原创不易,请大家多多支持。