携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第9天,点击查看活动详情
在年中总结的文章里,我提到过今年沉迷虚拟主播,并在虚拟主播的生日会上为主播制作了一个网页作为礼物,今天正好是(咕呱的)七夕,把它拿出来聊聊吧。
可以直接去看下效果:xiaoxideai.ren/
整个页面是这样的:
左右侧的装饰和鸣谢列表之类的不说,重点就是中间的千图成像部分,中间的图像实际上是由很多幅她的小图按照颜色组合而成的大图,当鼠标移上去的时候小图会呈现出来:
这个千图成像的算法我是拿nodejs来写的,具体怎么实现的因为篇幅有限所以回头专门说(才不是因为我自己写的代码自己都看不懂了呢),今天只讲讲前端页面的交互是怎么做的。
每个小图都是可以点击的,点击之后会出现这幅小图的全貌以及收集来的很多粉丝的生日祝福:
每张小图点击过一次之后,同样的图片区域会翻过来,出现另一幅图的一部分,最终需要通过翻开所有小图来展现一副完整图(她自己画的):
完整图呈现之后,出现PPT中打字机效果那样的,我给她写的专属祝福:
每个小图的点击事件
按照刚才的交互逻辑,每个小图都要绑定各自的点击事件,一开始的设想是把小图排列在各个位置,但是这样一来一打开就要加载25*25=625张图片,页面渲染和请求一定会出问题,因此我选择了将小图们拼接好的整幅图当做一张图片显示出来,然后在上面覆盖透明DOM,使用flex布局让其铺满整个中心区域:
<div class="center">
<!-- 完整图 -->
<img class="main" src="./main625.png"/>
<!-- 625个透明DOM,部分属性跟这个交互无关暂时省略了,后面第三个交互处是全部的 -->
<div v-for="(item,index) of grid" @click.stop="clickGrid(index)" v-bind:key="item.id" class="grid"></div>
</div>
.center {
width: 70vh;
height: 70vh;
margin: auto;
position: relative;
top: 0;
left: 0;
display: flex;
flex-wrap: wrap;
z-index: 4;
}
.grid {
width: 4%;
height: 4%;
background: white;
background-size: cover;
opacity: 0.3;
position: relative;
z-index: 2;
}
此外,我在生成千图成像的时候,同时生成了每个小图对应索引位置的顺序,类似于这样的数据:
{
"dduser": "超能诺诺",
"content": "每每看着你便不自觉地打起精神,愿带给大家元气与活力的小希在今后的日子里能永远与快乐相伴,相信闪闪发亮的你能获得越来越多的认可和期待,祝最可爱的小希——生日快乐!",
"used": true,
"index": 86,
"author": "超能诺诺",
"uid": "88663304",
"url": "./images/超能诺诺_88663304/17812b99d4a889fec5d5b05a35047615e66fe6d3.webp"
}
所以就可以根据每个空白DOM的索引位置进行图片和祝福语的呈现:
<div v-if="currentData" @click="hideImg()" class="blackCover">
<div @click="next()" class="next">❯</div> -->
<div class="postcard">
<div class="image">
<img @click.stop="showImg(currentData.url)" style="cursor:pointer" :src="`/static/${currentData.url}`" alt="">
<div class="author" @click.stop="toSpace(currentData.uid)">图:<span :class="currentData.uid ? 'toUid' : ''">{{currentData.author}}</span></div>
</div>
<div class="bless" @click.stop="()=>{}">
<div class="content">{{currentData.content}}</div>
<div class="dduser">{{currentData.dduser}}</div>
</div>
</div>
</div>
clickGrid(index) {
if (this.showSurpirse || this.grid[index]) return
this.currentData = database.find(item=>item.index == index)
let url = this.database.find(item=>index === item.index).url
// database就是上面说的数据
this.database.filter(item=>item.url === url).forEach(item => {
this.grid[item.index] = true
})
// 保存已经点击过的小图
localStorage.setItem('grid',JSON.stringify(this.grid))
},
点开小图后的图片翻转
点卡小图后,小图的位置会翻开成另一个图的一部分,以达到最后翻开全部图片的目的。这时候也面临着与前一个交互类似的问题:加载625张图片从浏览器性能和服务器开销上来说都不是最优选择,而这个时候,625个位置是为了呈现最后那张图的625个区域的内容,透明DOM显然不可行了,这时候我用到了CSS背景的background-positon属性:
<div
:style="{'background-position':`${index % 25 * 4}% ${Math.floor(index / 25) * 4}%`}"
:class="{'gridRead':item,'showBackground':item}"
@mouseleave="showMoasic = false"
v-for="(item,index) of grid"
@click.stop="clickGrid(index)"
v-bind:key="item.id"
class="grid"
></div>
实际上就是625个小块同样将整幅图设定成了背景图片,通过更改每个小块背景图片的位置(图大块小),使得每个小块呈现大图不同部分的内容,原理如下:
具体内容可以参考Mozilla的background-position属性文档
类似PPT的打字机效果
实际上就是将字符串使用split方法分割成数组,之后调用setinterval按照固定时间间隔将文字追加进去(当然还得益于MVVM的机制省的频繁操纵DOM了):
happyBirthDay() {
let bless = `选择了一幅你自己的画作为礼物,这幅画让我想到那个因为整晚没有击败盖侬而哭鼻子的小希,她却不知道出了台地就单挑盖侬已经击败了多少玩家,看着这个小希的背影,我知道她有时会很脆弱,但往往最后都会打起精神,甚至眼泪还没擦干,就会像这幅画一样,坚定的勇往直前。小希,四岁生日快乐!愿你一如既往,愿你心有暖阳~`
let splitBless = bless.split("")
let index = 0
let interval = setInterval(() => {
if (!splitBless[index]) {
clearInterval(interval)
this.showResetPage = true
return
}
this.supriseBless += splitBless[index]
index++
},300)
},
这时候音乐也随之切换成了她自己唱的一首歌的副歌部分,很励志的一段歌词:
总有人开拓着未来
总有美好不必倒带
总有圆满值得期待
世界陆离光怪
转眼桑田沧海
何必只惦念 互相伤害
不论在 深山或山外
都有人 传递着色彩
隔离也 分割不了爱
别沉沦 在失败
别辜负 了等待
惨淡过后是 苦尽甘来
后来就有人跟我说:
你要把这心思用在谈恋爱上,还能一直单身这么多年??
我:虽然很不爽,但是找不到什么反驳的理由……