本文主要锻炼的是原生JavaScript
的编程能力,运用了函数式编程的思想!
主要分成两个页面:a.初始呈现出来的界面;b.点击开始进入游戏的界面。
a界面: 比较丑,大家注重功能实现就好,忽略ui
说明:
a.画页面不是我的目的,所以背景潦草的使用了一张图片简单实现了一下。
b.三个dom
元素:两个按钮(开始、说明),点击说明按钮出现的说明内容。
// 初始呈现出来的界面
<div id="gameStart">
<div id="start">div>
<div id="describe">div>
<div id="des">
我是一段认认真真的游戏说明。
<div id="cl">关闭div>
div>
div>
/* container 是最外层的包裹容器 */
#container{
/* 宽高就是背景图片的尺寸 */
width: 521px;
height: 342px;
margin: 50px auto;
/* 设置相对定位 */
position: relative;
/* 后续下落字母落到背景图以下时候,进行隐藏 */
overflow: hidden;
}
/* 把背景图片放进 gameStart 里 */
#gameStart{
background: url(./img/background.png) no-repeat;
/* 宽高百分之百,继承父级 */
width: 100%;
height: 100%;
/* display: none; */
}
/* 按钮的定位,两按钮除了距离底部高度不一致外其余都相同 */
#start, #describe{
width: 101px;
height: 30px;
/* border: 1px solid black; */
position: absolute;
left: 7px;
border-radius: 20px;
cursor: pointer;
}
#start{
bottom: 51px;
}
#describe{
bottom: 6px;
}
/* 游戏说明部分 */
#des{
width: 300px;
height: 100px;
position: absolute;
top: 100px;
left: 100px;
border: 7px solid skyblue;
background-color: #fff;
text-align: center;
display: none;
}
/* 关闭叉叉 */
#cl{
position: absolute;
top: 0;
right: 0;
cursor: pointer;
border: 1px solid #ccc;
font-weight: bold;
display: none;
}
b.界面: 点击开始按钮进入游戏界面
四个操作按钮:
<div id="game">
<div id="oprate">
<span class="start">开始span>
<span class="exit">退出span>
<span class="finish">结束span>
<span class="set">设置span>
div>
<div id="select">
<select name="" id="">
<option value="3">慢option>
<option value="2">中option>
<option value="1">快option>
select>
<span id="close">关闭span>
div>
<div id="tip">
<p>得分: <span>0span> 分p>
<p>正确率:<span>0 %span>p>
<p>速度:<span>0 span>个 / 分p>
div>
div>
/* 一开始游戏界面是隐藏的 */
#game{
display: none;
width: 100%;
height: 100%;
border: 1px solid #666;
background-color: #ccc;
}
/* 四个选项:开始、退出、设置、结束 */
#oprate{
position: absolute;
bottom: 0;
left: 0;
width: 100px;
height: 100px;
}
#oprate span{
display: inline-block;
width: 40px;
height: 40px;
text-align: center;
line-height: 40px;
border-radius: 50%;
cursor: pointer;
position: absolute;
background-color: skyblue;
color: #fff;
font-weight: bolder;
}
#oprate span:hover{
background-color: green;
}
#oprate span:nth-child(1) {
left: 0;
top: 30px;
}
#oprate span:nth-child(2) {
left: 30px;
top: 0;
}
#oprate span:nth-child(3) {
left: 30px;
bottom: 0;
}
#oprate span:nth-child(4) {
top: 30px;
right: 0;
}
/* 点击设置弹出游戏难度选择 */
#select{
display: none;
width: 140px;
height: 60px;
border: 7px solid skyblue;
position: absolute;
top: 50px;
left: 50px;
background-color: #fff;
}
#select select{
width: 80px;
height: 30px;
text-indent: 22px;
font-size: 19px;
margin-left: 30px;
margin-top: 15px;
}
#close{
position: absolute;
top: 0;
right: 0;
cursor: pointer;
display: none;
}
#close:hover{
background-color: pink;
}
/* 提示打字的得分、正确率和速度 */
#tip{
position: absolute;
top: 0;
right: 0;
width: 150px;
line-height: 30px;
padding: 5px 10px;
letter-spacing: 2px;
color: red;
opacity: 0.5;
}
END: 至此,结构样式部分全部结束
JavaScript
部分:
1.封装函数拿到所有需要的DOM
节点。
function $(idName) {
return document.getElementById(idName);
}
var gameStart = $('gameStart');
var start = $('start');
var describe = $('describe');
var des = $('des');
var cl = $('cl');
var game = $('game');
var oprate = $('oprate');
var close = $('close');
需求A:点击开始游戏,隐藏游戏初始界面,显示进入游戏界面。
// 开始游戏的按钮的 id=start
// gameStart是初始界面最外层的id
// game是游戏开始界面最外层的id
// 思路很简单: 让页面最外层包裹元素display=none/block;即可
start.onclick = function() {
gameStart.style.display = "none";
game.style.display = "block";
}
需求B:进入游戏界面之后点击退出按钮实现页面回到初始界面。以及设置按钮。
// 使用了事件委托,将事件全部委托到父元素上实现功能
// oprate是包裹四个操作按钮元素的父容器id
// 事件委托,将子元素的点击事件全部赋到父元素上,点击父元素触发子元素的点击事件
oprate.onclick = function(event) {
// 事件兼容处理,兼容 IE
var e = event || window.event;
var target = e.srcElement || e.target;
// 退出
if(target.className === 'exit') {
gameStart.style.display = "block";
game.style.display = "none";
}
// 设置,出现游戏难度
if(target.className == 'set') {
select.style.display = 'block';
}
}
需求C:初始页点击游戏说明按钮显示游戏说明及关闭。游戏难度
// 点击说明按钮显示游戏说明
// describe是游戏说明按钮的 id
// des是说明内容的 id
// cl是关闭文字的 id
// select是设置游戏难度最外层的 id
// close是关闭文字的 id
describe.onclick = function() {
des.style.display = 'block';
}
// 鼠标经过游戏说明区域时,显示关闭按钮
des.onmouseover = function() {
cl.style.display = 'block';
}
// 鼠标移除时候隐藏
des.onmouseout = function() {
cl.style.display = 'none';
}
// 关闭游戏说明内容
cl.onclick = function() {
des.style.display = 'none';
}
// 游戏难度的关闭按钮
select.onmouseover = function() {
close.style.display = 'block';
}
select.onmouseout = function() {
close.style.display = 'none';
}
// 点击关闭设置游戏难度的按钮
close.onclick = function() {
select.style.display = "none";
// 当我们进行游戏难度设置以后,关闭游戏难度设置之后,level数值生效!
level = selFir.value;
}
2.封装函数,获取到元素使用样式的最终值,保证兼容。
function getStyle(ele, attr) {
// 定义变量,用以保存最终获取到的值
var res = null;
// 判断当前浏览器是否支持 currentStyle 这个属性
if(ele.currentStyle) {
// 有这个属性的话,使用ele对象的currentStyle属性来获取 attr 元素属性,并储存
res = ele.currentStyle[attr];
}else {
// 否则
res = window.getComputedStyle(ele, null)[attr];
}
// 将储存的值返回出去
return parseFloat(res);
}
3.封装运动函数。
// 获取游戏界面的高度和宽度
var gameH = getStyle(gameStart, "height");
var gameW = getStyle(gameStart, "width");
// 运动的元素 运动到的最终值 运动元素哪个属性在变化
function startMove(ele, end, attr) {
// 控制字母下落速度
// 随着分数越来越高,让速度越来越快
var speed = 0.5 + score / 100;
// 将定时器赋值给一个变量,以便后续去清除
ele.timer = setInterval(function() {
// 获取当前元素的运动值
var moveVal = getStyle(ele, attr);
if(moveVal >= end) {
clearInterval(ele.timer);
// 删除元素,防止长时间页面卡死
game.removeChild(ele);
// 当ele 目标元素达到清除的时候,就让目标元素 ele 里面的内容清除,最后再删除掉
letters = letters.replace(ele.innerHTML, '');
}else {
ele.style[attr] = moveVal + speed + 'px';
}
}, 10)
}
需求d:进入游戏界面后的开始和暂停游戏
注意:该部分是位于需求2中的事件代理下的后续if
操作。
// 定时器
var c;
// 动态创建的所有字母元素,我怎么去获取呢?去到创建字母的函数中看看
var letterEles;
// 如果用户点击了开始按钮
if(target.className === 'start') {
target.innerHTML = target.innerHTML == "开始" ? "暂停" : '开始';
// 游戏的暂停
if(target.innerHTML == '开始') {
// 当前状态是暂停的时候,游戏的设置功能开启
oprate.lastElementChild.style.cursor = 'pointer';
clearInterval(c);
// 重置 c
c = undefined;
// 清除所有字母元素上的定时器
clearAllLetters();
}else {
// 当前状态是开始的时候,不允许点击游戏设置按钮
oprate.lastElementChild.style.cursor = 'not-allowed';
// 游戏的开始
// 注意: 当我们反复点击开始暂停按钮的时候,需要判断当前是否已经存在定时器了,已经存在就不再开启了,防止开启多个定时器之后页面卡死。
if(c) {
return;
}
// 定义开始时间,用以统计打字速度
startTimeStamp = new Date() * 1;
// 设置定时器,每隔0.5s下落一个文字
c = setInterval(function() {
// 定义结束时间,用以统计打字速度
endTimeStamp = new Date() * 1;
// 不满1分钟按1分钟进行计算
if(endTimeStamp - startTimeStamp <= 60 * 1000) {
// tip.children[2].firstElementChild 找到速度
tip.children[2].firstElementChild.innerHTML = score;
}else {
// 超过1分钟不足两分钟
// Math.ceil((endTimeStamp - startTimeStamp)/(60*1000)) 得到分钟
tip.children[2].firstElementChild.innerHTML = Math.ceil(score/Math.ceil((endTimeStamp - startTimeStamp)/(60*1000)));
}
// 下面创建字母的封装函数
createLetter();
console.log(letters);
// 拿到所有字母所在的元素,看下面封装函数4,发现每个创建的元素的class类名都是 active
// 防止通过 className 获取到的方式不兼容所有浏览器,下面进行兼容处理
letterEles = document.getElementsByClassName('active');
// 通过改变 level 的数值改变游戏进行的快慢
}, level * 1000)
// 暂停之后的开始游戏
gameStarts();
}
}
兼容处理:如果用户浏览器不支持document.getElementsByclassName
方法,默认使用下面这个我们封装的方法。
if(!document.getElementsByClassName) {
document.getElementsByClassName = function(clsName) {
// 获取所有标签元素
var all = document.all;
// 放数组容器,进行遍历筛选
var all = [];
for(var i = 0;i < all.length;i ++) {
Array.push(all[i]);
}
return all;
}
}
4.封装函数,创建下落字母。
.active{
position: absolute;
top: -30px;
width: 30px;
height: 30px;
border-radius: 50%;
text-align: center;
line-height: 30px;
color: #fff;
font-weight: bolder;
}
function createLetter() {
var span = document.createElement('span');
// 赋予样式
span.className = 'active'
// 随机创建一个字母
var l = randLetter();
// 将字母插入到 span 里面
span.innerHTML = l;
// letters全局变量是存放所有随机产生的容器,需求g中有定义
letters += l;
// left数值 = 游戏界面的宽度 - 一个字母的宽度30
span.style.left = Math.floor(Math.random() * (gameW - 30)) + 'px';
// 使用下面封装的随机颜色函数
span.style.background = randBg();
// 创建完成之后追加到游戏界面中
game.appendChild(span);
// 字母运动
startMove(span, gameH, "top");
}
5.封装函数,随机产生字母。
function randLetter() {
var str = "abcdefghijklmnopqrstuvwxyz";
// 将大写的字母也包含进去
str += str.toUpperCase();
return str.charAt(Math.floor(Math.random() * str.length));
}
6.封装函数,生成16进制随机颜色值。
function randBg() {
var str = '0123456789abcdef';
var colorVal = '#';
for(var i = 0;i < 6;i ++) {
colorVal += str.charAt(Math.floor(Math.random() * str.length));
}
return colorVal;
}
7.封装函数,清除掉所有字母所在元素的定时器。
function clearAllLetters() {
for(var i = 0;i < letterEles.length;i ++) {
clearInterval(letterEles[i].timer);
}
}
8.封装函数,暂停之后点击开始按钮,继续开始游戏
function gameStarts() {
// 因为在调用这个函数的时候由于定义的letterEles还没有赋值,是undefined,所以我们这边进行排除一下
if(!letterEles) return;
for(var i = 0;i < letterEles.length;i ++) {
startMove(letterEles[i],gameH,"top");
// 在游戏开始的时候调用
}
}
9.封装函数,结束游戏。
function finished() {
// 清除单位时间内产生字母的定时器
clearInterval(c);
c = undefined;
// 当点击结束按钮的时候,将得分,速度,正确率都清零
score = 0;
s = 0;
accu = "0%";
// 清零完成后的重新加载数据
tip.children[0].firstElementChild.innerHTML = score;
tip.children[1].firstElementChild.innerHTML = accu;
tip.children[2].firstElementChild.innerHTML = s;
// 删除所有字母
for(var i = letterEles.length - 1;i >= 0;i --) {
// 从父元素开始查找
game.removeChild(letterEles[i]);
}
// 此时游戏画面已经清空,但是按钮不知道停留在什么状态。下面进行一步判断,将按钮状态调整为待开始状态。
if(oprate.firstElementChild.innerHTML == '暂停') {
oprate.firstElementChild.innerHTML = '开始'
}
}
需求e: 处理结束游戏
注意:该部分是位于需求2中的事件代理下的后续if
操作。
// 处理结束游戏
if(target.className == 'finish') {
finished();
}
// 处理退出游戏
if(target.className == 'exit') {
// 首先处理结束游戏
finished();
// 显示游戏开始的界面,隐藏进入游戏的界面
game.style.display = 'none';
gameStart.style.display = 'block';
}
需求f:设置游戏难度
<div id="select">
<select name="" id="">
<option value="3">慢option>
<option value="2">中option>
<option value="1">快option>
select>
<span id="close">关闭span>
div>
var selFir = select.firstElementChild;
// 默认游戏难度是慢的
var level = 3;
需求g:实现键盘打字,字母消失。
// 声明一个全局变量容器,用来存放 createLetter 函数所创建出来的字母
var letters = ''
需求h:键盘事件,敲击键盘,实现dom
节点的消失
// 如果使用`onkeypress`的话,会区分大小写,这里我们不需要去区分
document.onkeyup = function(evt) {
// 兼容处理
var e = evt || window.event;
var codeVal = e.keyCode;
console.log(codeVal);
// 统计用户一共按了多少次规定范围下的按键,用于后面统计正确率
if(codeVal >= 65 && codeVal < 90) {
count ++;
}
// 根据键值找到对应的字符
var char = keyVal[codeVal];
if(char) {
var index = letters.search(eval('/' + char + "/gi"));
// index != -1; 说明找到了
if(index != -1) {
// 将对应元素的 dom 节点干掉
game.removeChild(letterEles[index]);
// 全局匹配 + 忽略大小写
var exp = eval('/' + char + '/gi');
// 将 exp 匹配成 ''
letters = letters.replace(exp, '');
// 成功消灭了一个 dom 元素,得一分
tip.firstElementChild.firstElementChild.innerHTML = ++ score;
endTimeStamp = new Date() * 1;
if(endTimeStamp - startTimeStamp <= 60 * 1000) {
tip.children[2].firstElementChild.innerHTML = score;
}else {
tip.children[2].firstElementChild.innerHTML = Math.ceil(score/Math.ceil((endTimeStamp - startTimeStamp)/(60*1000)));
}
}
// 实现正确率,toFixed(2)保留两位小数位
// 将数值插入到 tip 下的第二个子元素的第一个元素内容部分
tip.children[1].firstElementChild.innerHTML = (score / count * 100).toFixed(2) + '%'
}
// console.log(char)
}
需求i:找到键值
// 新建一个 js 文件,命名为 keyValue.js
var keyVal = {
};
var str = 'abcdefghijklmnopqrstuvwxyz';
// a键的键值是65,往后加26个字母
for(var i = 65;i < 90;i ++) {
// 将对应字符以及其键值相对应。
keyVal[i] = str.charAt(i - 65);
}
console.log(keyVal)
需求j:实现得分、正确率、速度。以下是定义全局变量,具体实现步骤在以上需求内。
// count 键盘一共按下多少次
var score = 0,accu = '0%',s = 0,count = 0;
// 定义开始时间和结束时间
var startTimeStamp = null,endTimeStamp = null;
总体实现思路总结:
以上便是全部的讲解
源码我放在了github上,欢迎下载!!!