文末
js前端的重头戏,值得花大部分时间学习。
推荐通过书籍学习,《 JavaScript 高级程序设计(第 4 版)》你值得拥有。整本书内容质量都很高,尤其是前十章语言基础部分,建议多读几遍。
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
另外,大推一个网上教程 现代 JavaScript 教程 ,文章深入浅出,很容易理解,上面的内容几乎都是重点,而且充分发挥了网上教程的时效性和资料链接。
学习资料在精不在多,二者结合,定能构建你的 JavaScript 知识体系。
面试本质也是考试,面试题就起到很好的考纲作用。想要取得优秀的面试成绩,刷面试题是必须的,除非你样样精通。
这是288页的前端面试题
audio.src = src
audio.load()
audio.volume = .5
audio.play()
}
}
这里播放音效的时候需要判断当前的声音开关是否打开,如果打开的话在进行播放,注意,这里不能通过给单一的audio对象改变地址的方式播放不同的音效,因为如果在当前声音正在播放时候,修改音效地址会报错。
弹幕
–
这个创意是我在听春节序曲的背景音乐时想到的,因为一听这个就想到春晚,想到短片中全国各地的人民送祝福,于是我就想把这个加进来,结合背景音乐,是不是一下子感觉就来了。也希望大家可以送上自己的祝福,我也会把你的祝福更新到弹幕里的。这里的弹幕就只为了满足游戏的需求,不会太复杂。
首先,我们需要梳理一下弹幕的需求和注意点
-
弹幕横向和纵向不能重叠
-
两天弹幕之间的间隔最好可以随机
-
弹幕超出屏幕要自动移除
首先说弹幕不能重叠的问题,弹幕纵向不能重叠的话,我们就需要有一个弹道的概念,也就是让每一条弹幕都有自己的轨道,各走各的,当然就不会重叠了。我这里是根据屏幕高度,分成了10个弹道,本来打算屏幕越大,弹道越多的,但是考虑到性能问题,就采用了这种方案。
其次就是弹幕横向的防止重叠,我百度的时候看到其他作者提到的追及问题什么的,奈何我是个学渣,没有看太明白,于是就自己想了解决办法,我们这里每条弹幕的移动速度是一样的,那需要考虑的就是每条弹幕出现的时机问题了,我们需要在同一弹道的前一条弹幕完全出现后,再生成下一条弹幕,中间可以加一个我们规定好范围的随机距离,这样更美观一点。
下面来看一下代码怎么实现的。
ballistic: 0, // 弹道数量
bulletSpeed: 2, // 弹幕速度
bulletInterval: [300, 500], // 弹幕间隔
screenWidth: document.documentElement.clientWidth, // 屏幕宽度
screenHeight: document.documentElement.clientHeight, // 屏幕高度
/**
-
@description: 展示弹幕
-
@param {*}
-
@return {*}
*/
showBullet () {
// 此处直接设定了10条弹道,也可根据屏幕高度和弹幕高度计算弹道数
let ballisticArr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
// 按随机顺序在所有的弹道添加弹幕
let ballisticLaunch = () => {
let randomIndex = Math.floor(Math.random() * ballisticArr.length)
let ballisticIndex = ballisticArr.splice(randomIndex, 1)[0]
this.createBullet(ballisticIndex)
if (ballisticArr.length > 0) {
setTimeout(ballisticLaunch, Math.random() * 1000)
}
}
ballisticLaunch()
// this.createBullet(2)
},
我这里的方法是先设定好弹道数,然后把这些的弹道的序号放进一个数组,开始时直接从这个数组去取编号,往这个弹道放进去一个弹幕,然后循环,直到每一条弹道都被用完为止,那么问题来了,这时候我们每条弹道只有一条弹幕,怎么生成后续弹幕呢,这里的思路是在每一条弹幕移动的时候,判断自己的移动距离,当达到合适的距离时(自身完全出现在屏幕中并且距离屏幕右侧达到了我们设定的两条弹幕间的距离)就调用加载下一条弹幕的方法,并把自身的弹道编码传入,加上我们这里弹幕是匀速的,就不会有重叠的问题了。
/**
-
@description: 添加弹幕
-
@param {*} index 弹道索引
-
@return {*}
*/
createBullet (index) {
let bullet = document.createElement(‘div’)
let bulletHeight = document.documentElement.clientHeight / 10
bullet.className = ‘bullet-chat’
bullet.style.left = this.screenWidth + ‘px’
bullet.style.top = index * bulletHeight + ‘px’
bullet.createNext = false // 是否已创建下一个弹幕
bullet.nextSpace = Math.random() * (this.bulletInterval[1] - this.bulletInterval[0]) + this.bulletInterval[0] // 下一个弹幕间隔
// 从弹幕库随机取弹幕
let dataLength = this.blessingData.length
let randomIndex = Math.floor(Math.random() * dataLength)
let blessing = this.blessingData[randomIndex]
bullet.innerText = blessing.name + “:” + blessing.value
this.$refs.bulletChat.appendChild(bullet)
// 弹幕移动
let bulletMove = () => {
bullet.style.left = bullet.offsetLeft - this.bulletSpeed + ‘px’
if (!bullet.createNext) {
// 如果弹幕距离屏幕右侧距离超出弹幕间隔,则加载下一条弹幕
if (bullet.offsetLeft < (this.screenWidth - bullet.offsetWidth - bullet.nextSpace)) {
this.createBullet(index)
bullet.createNext = true
}
}
// 如果弹幕距离右侧距离大于等于屏幕宽度,则移除弹幕
if (bullet.offsetLeft < (-bullet.offsetWidth)) {
this.$refs.bulletChat.removeChild(bullet)
} else {
requestAnimationFrame(bulletMove)
}
}
bulletMove()
}
这里我们引入了一个弹幕库,每次从中随机取一条,这样就避免旧弹幕无法被看到的问题了,另外大家也都看到了,这里用的定时方法是requestAnimationFrame,这个真的比setinterval要好,本项目基本所有用到动画的地方都用的这个,也建议大家都用这个方法代替setinterval,好处比较多,这里就不占字数了,大家感兴趣自行百度吧。
年兽
–
这个可爱的小东西就是我们的年兽了,年兽的组成很简单,一个小图标,加一个血量,然后我们让它来回动起来就可以了。当血量为0时候我们就让它消失。
class=“nianshou”
:style=“‘marginLeft:’ + nianshouLeft + ‘px’”
v-show=“nianshouHP”
HP: {{ nianshouHP }}
nianshouLeft: 0, // 年兽距离左边的距离
nianshouMove () {
// 更新游戏时间
this.gameDuration = new Date().getTime() - this.gameBeginTime
if (this.nianshouLeft + 200 >= this.screenWidth) {
this.nianshouMoveDir = -4
} else if (this.nianshouLeft < 0) {
this.nianshouMoveDir = 4
}
this.nianshouLeft += this.nianshouMoveDir
this.nianshouInterval = requestAnimationFrame(this.nianshouMove)
},
我们的游戏规则是用时越少越厉害,所以我们需要计算游戏用时多少,这里我们以年兽开始移动时为游戏开始时间,另外我们还需要在年兽撞墙的时候往反方向运动,所以这里我们判断了年兽距离屏幕左边和右边的距离,一旦达到界定值的时候,则改变移动方向,也就是改变移动值的正负
炮竹
–
这个小玩意儿就是我们的炮竹了,也相当于我们的武器,我本来想找一个烟花筒来释放烟花的,奈何资源有限,就用这个将就吧。这个小炮竹会不断的发出光束去打年兽,这里关于炮竹,就是鼠标按下的时候添加移动事件,让他左右移动就可以了。
第一步肯定就是炮竹的移动,这个我们不做的太复杂,直接让鼠标拖动进行左右移动就行了,不让上下移动是为了你举着炮竹往年兽脸上怼。
思路,鼠标点击炮竹,给整个区域添加移动事件,不给炮竹添加移动事件时因为鼠标移动过快的话很容易超出炮竹的范围,造成不好的游戏体验,当鼠标抬起时,我们再把这个事件给移除。至于移动,我们需要先定义一个clientx
,每次鼠标移动的时候存储鼠标距离屏幕左侧的距离,当鼠标再次移动的时候,我们用当前光标距离左侧的距离建议刚刚存储的,就可以得出鼠标移动的距离,然后我们把这个值的变化赋值给炮竹的margin-left
class=“paozhu”
ref=“paozhu”
@mousedown=“addMove”
:style=“‘marginLeft:’ + paozhuLeft + ‘px’”
clientX: 0, // 鼠标上次的位置
paozhuLeft: 0 // 炮竹距离左边的距离
// 鼠标按下,添加移动事件
addMove (e) {
e = e || window.event
this.clientX = e.clientX
this.clientY = e.clientY
this.$refs.gemeWrap.onmousemove = this.moveFunc
},
// 鼠标拖动,移动炮竹
moveFunc (e) {
e = e || window.event
e.preventDefault()
let movementX = e.clientX - this.clientX
this.paozhuLeft += movementX
this.clientX = e.clientX
},
// 鼠标抬起,移除移动事件
removeMove () {
this.$refs.gemeWrap.onmousemove = null
},
子弹
–
我们暂且称炮竹发出的光束为子弹吧,子弹的实现原理很简单,定时发射子弹,发射子弹时获取炮竹的横向坐标,再以屏幕高度减去炮竹高度为纵向坐标,生成之后让子弹往上跑就行了,当子弹距离顶部距离小于等于年兽的高度时,判断子弹的横向坐标是否和年兽的横向坐标重合,如果重合就对年兽扣血,播放击中音效,移除子弹,如果未重合,则在子弹跑出屏幕时移除子弹。
这里我们设置了一个子弹飞行速度,如果你玩过了游戏,一定会发现,刚开始不好射中吧,哈哈哈,这也算是增加了难度,当然,如果答对了问题,射速,攻速,伤害都会相应的增加。
createBulletInterval: null, // 创建子弹的定时器
frequency: 5, // 发射子弹频率
bulletSpeed: 10, // 子弹飞行速度
damage: 2,// 子弹攻击力
lastBulletTime: 0, // 上次发射子弹时间
// 生成子弹
createBullet () {
// 子弹
let now = new Date().getTime()
if (now - this.lastBulletTime > (1000 / this.frequency)) {
let bullet = document.createElement(‘div’)
bullet.className = ‘bullet’
bullet.style.left = this.paozhuLeft + 25 + ‘px’
bullet.style.top = this.screenHeight - 123 + ‘px’
this.$refs.gemeWrap.appendChild(bullet)
this.$store.commit(‘playAudio’, require(‘…/assets/mp3/emit.mp3’))
// 子弹移动
let bulletMove = () => {
bullet.style.top = bullet.offsetTop - this.bulletSpeed + ‘px’
// 如果子弹距离顶部的距离为年兽的高度时,判断子弹和年兽的水平位置是否重合
if (bullet.offsetTop <= 250 && bullet.offsetLeft >= this.nianshouLeft && bullet.offsetLeft <= this.nianshouLeft + 200) {
// 年兽掉血
this.nianshouHP -= this.damage
this.$store.commit(‘playAudio’, require(‘…/assets/mp3/boom.wav’))
if (this.nianshouHP <= 0) {
this.nianshouHP = 0
this.gameOver()
}
// 子弹消失
this.$refs.gemeWrap.removeChild(bullet)
// cancelAnimationFrame(bulletMove)
} else if (bullet.offsetTop <= 0) {
this.$refs.gemeWrap.removeChild(bullet)
// cancelAnimationFrame(bulletMove)
} else {
requestAnimationFrame(bulletMove)
}
}
bulletMove()
this.lastBulletTime = now
}
this.createBulletInterval = requestAnimationFrame(this.createBullet)
}
由于requestAnimationFrame不能设置间隔时间,所以这里我们就在生成子弹的时候记录下生成子弹的时间,在requestAnimationFrame下一次运行的时候,判断时间间隔是否满足我们对子弹频率的要求,如果满足则往下执行,如果不满足跳过本次执行。
问题
–
本游戏的一大特色,就是加入了答题系统,否则一直在那biubiubiu的打怪兽有啥意思呢,年兽的血量为2021,靠初始攻速和伤害得打半天,如果答对问题,则会增加buff,打年兽能力蹭蹭的往上涨。
首先来分析一下问题的需求
-
每道题的答题时间是8秒钟,无论是否提前选择均展示8秒
-
答对题目则增加buff
-
答错或者在倒计时结束未选择答案将展示正确答案
-
每道题的间隔时间是5秒钟
-
每次出题从题库随机取题,出现过的题目不会第二次抽取
先从最简单的开始,从题库抽取题目
questionJson: require(‘@/assets/data/question.json’), //问题源数据
questionData: [], // 本轮游戏题库
questionList: [],// 问题列表
let dataLength = this.questionData.length
let randomIndex = Math.floor(Math.random() * dataLength)
let question = this.questionData.splice(randomIndex, 1)[0]
很简单,接下来就是添加倒计时,先加的是题目间隔倒计时,在一道题目被添加时候,展示5秒钟倒计时,然后展示题目并开始答题倒计时
// 添加展示倒计时
let showCountDown = () => {
data.showTime–
if (data.showTime > 0) {
setTimeout(showCountDown, 1000)
} else {
// 倒计时结束,展示问题并开始答题倒计时
answerCountDown()
}
}
接下来是答题倒计时,游戏设置的题目是5道,每道题结束会先判断用户是否作答,如果没有作答,自动将结果设置为错误答案,之后再判断题目是否达到5道,如果没有达到则继续添加,直到够5道为止。
// 添加回答倒计时
let answerCountDown = () => {
data.answerTime–
if (data.answerTime > 0) {
setTimeout(() => {
showCountDown()
}, 1000)
} else {
// 倒计时结束,如果没有选择正确答案,则添加一道错误答案
if (!data.result) {
data.result = ‘2021’
}
// 如果问题不足5道,则添加一道问题
if (this.questionList.length < 5) {
this.addQuestion()
}
}
}
在接下来就是答题了,先来看一下问题面板的dom结构
class=“question-panel panel-item”
:class=“{ clientCenter: question.answerTime > 0 }”
v-for=“(question, index) in questionList”
:key=“index”
{{ question.showTime }}
请在{{ question.answerTime }}秒内点击正确答案
最后
小编综合了阿里的面试题做了一份前端面试题PDF文档,里面有面试题的详细解析
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
虽只说了一个公司的面试,但我们可以知道大厂关注的东西并举一反三,通过一个知识点延伸到另一个知识点,这是我们要掌握的学习方法,小伙伴们在这篇有学到的请评论点赞转发告诉小编哦,谢谢大家的支持!