前言

移动端浏览器是没有实体键盘的,想要操作游戏就必须为其设置虚拟按键,通过虚拟按键(按钮)的标识与实体键盘的keyCode进行绑定,来达到想要的效果。

这个随笔只封装NES游戏手柄右边的按键,不包含方向键。方向键的封装在另一个章节。

1.按键UI

NES游戏手柄分为6个键,如图:

连发按键这里不做,我看到很多第三方网页模拟器也是没有的。不过虚拟键盘的A+B同按是很困难的,这里会为其增加一个宏按键。设计图如下:

封装的初衷在于用户只需要自己配置好外层容器的大小和id,其他的事交给插件去做。所以插件要自动适应容器大小,而且能动态添加dom元素到目标容器中。

虚拟按键设计思路:

(1)最外层的容器宽高100%,使用flex布局,它的子元素就是虚拟按键

(2)子元素分为3行,每个按键的宽度以百分比计算。

html容器结构:

<body>
<!-- user_btn_box 为用户设置的容器-->
<div id="user_btn_box">
<!-- nes_btn_box 为插件添加的容器,配合js动态添加-->
<div class="nes_btn_box">
<span class="btn btn-select">SELECT</span>
<span class="btn btn-start">START</span>
<span class="btn btn-ab">B + A</span>
<span class="btn btn-b">B</span>
<span class="btn btn-a">A</span>
</div>
</div>
</body>

样式文件:

.nes_btn_box {
width:100%;
height:100%;
display: flex;
flex-wrap: wrap;
justify-content: space-around;
align-content: space-around;
-webkit-tap-highlight-color: transparent;
} .nes_btn_box .btn {
height:30px;
line-height: 30px;
text-align: center;
background-color: lightcoral;
color: lightcyan;
cursor: pointer;
border-radius: 5px;
}
/* 点击时按钮的背景色 */
.nes_btn_box .btn.isTouch {
background-color: linen;
} .nes_btn_box .btn-select,
.nes_btn_box .btn-start {
width:40%;
}
.nes_btn_box .btn-ab {
width:80%;
height:60px;
line-height: 60px;
} .nes_btn_box .btn-a,
.nes_btn_box .btn-b {
width:40%;
height:60px;
line-height: 60px;
}

2.动态添加虚拟按键

插件其实就是一个构造函数,为其设置init方法,接收一系列参数来完成所需要的功能

(1)创建构造函数,接收参数

(2)为构造函数设置init方法,传入接收的参数

(3)通过传入的参数,创建dom元素,传入对应的容器中

插件代码:

function VirtualNesBtn(opt){
//接收容器的标识
this.el = opt.el //生成5个带随机数后缀的id
this.id_arr = []
this.id_arr.push('btn_select_' + Math.floor(Math.random() * 100000))
this.id_arr.push('btn_start_' + Math.floor(Math.random() * 100000))
this.id_arr.push('btn_ab_' + Math.floor(Math.random() * 100000))
this.id_arr.push('btn_b_' + Math.floor(Math.random() * 100000))
this.id_arr.push('btn_a_' + Math.floor(Math.random() * 100000)) //设定5个按钮的文本名称
this.name_arr = ['SELECT', 'START', 'B + A', 'B', 'A']
} //初始化 创建虚拟按钮
VirtualNesBtn.prototype.init = function(opt){
var me = this
//创建5个按键
var btn_select = document.createElement('span')
var btn_start = document.createElement('span')
var btn_ab = document.createElement('span')
var btn_b = document.createElement('span')
var btn_a = document.createElement('span') //为5个按键设置css类名
btn_select.classList.add('btn','btn-select')
btn_start.classList.add('btn','btn-start')
btn_ab.classList.add('btn','btn-ab')
btn_b.classList.add('btn','btn-b')
btn_a.classList.add('btn','btn-a') //为5个按键设置id
btn_select.id = me.id_arr[0]
btn_start.id = me.id_arr[1]
btn_ab.id = me.id_arr[2]
btn_b.id = me.id_arr[3]
btn_a.id = me.id_arr[04] //为5个按键设置 文本
btn_select.innerText = me.name_arr[0]
btn_start.innerText = me.name_arr[1]
btn_ab.innerText = me.name_arr[2]
btn_b.innerText = me.name_arr[3]
btn_a.innerText = me.name_arr[4] //创建容器,并将5个按钮插入其中
var nes_btn_box = document.createElement('div')
nes_btn_box.classList.add('nes_btn_box')
nes_btn_box.appendChild(btn_select)
nes_btn_box.appendChild(btn_start)
nes_btn_box.appendChild(btn_ab)
nes_btn_box.appendChild(btn_b)
nes_btn_box.appendChild(btn_a) //插入到目标容器中
var target = document.querySelector(me.el)
target.appendChild(nes_btn_box) //为按钮设置点击高亮效果
me.set_tap_highlight()
} //监听点击 设置点击高亮效果
VirtualNesBtn.prototype.set_tap_highlight = function(){
var me = this
var box = document.querySelector(me.el) box.addEventListener('touchstart',(evt) => {
//阻止默认事件,防止快速点击时页面放大
evt.preventDefault()
//判断点击的目标元素是否是虚拟按钮之一
if(me.id_arr.includes(evt.target.id)){
evt.target.classList.add('isTouch')
}
}) box.addEventListener('touchend',(evt) => {
//判断点击的目标元素是否是虚拟按钮之一
if(me.id_arr.includes(evt.target.id)){
evt.target.classList.remove('isTouch')
}
})
}

3.将虚拟按钮的touch事件绑定到实体键盘事件中

游戏模拟器本身是通过监听键盘事件,在回调函数中判断事件对象的keyCode属性来判断用户按下的是哪个按键,虚拟按钮的touch事件对象中没有keyCode属性,我们可以手动添加这个属性,供回调函数使用。

注意:宏按键(B+A)要单独处理

模拟器相关代码如图:

前面的点击高亮的函数已经监听了touch事件,这里也需要监听touch事件,为了代码复用,要对刚才的代码进行优化。

创建实例时,接收4个属性,分别是:

el:容器的标识(必须),例如 '#user_box'

btn_down_fn:虚拟按钮按下时的回调(必须)

btn_ip_fn:虚拟按钮释放时的回调(必须)

keyCodes:[] 按顺序分别是 select start b a 对应的keyCode (可选)

对元素进行touch监听时,通过evt.target.id可以获取目标元素的id,为了方便根据id获得其绑定的keycode,封装一个方法。

获取keyCode后,进行一系列判断,并调用相关回调。

function VirtualNesBtn(opt){
//接收容器的标识
this.el = opt.el
//接收回调
this.btn_down_fn = opt.btn_down_fn //fn
this.btn_up_fn = opt.btn_up_fn //fn //保存按钮信息
this.btns_info = [] //为5个按钮添加带数字后缀的 id
this.btns_info = [{},{},{},{},{}]
this.btns_info[0].id = 'btn_select_' + Math.floor(Math.random() * 100000)
this.btns_info[1].id = 'btn_start_' + Math.floor(Math.random() * 100000)
this.btns_info[2].id = 'btn_ab_' + Math.floor(Math.random() * 100000)
this.btns_info[3].id = 'btn_b_' + Math.floor(Math.random() * 100000)
this.btns_info[4].id = 'btn_a_' + Math.floor(Math.random() * 100000) //设定5个按钮的文本名称 name
this.btns_info[0].name = 'SELECT'
this.btns_info[1].name = 'START'
this.btns_info[2].name = 'B + A'
this.btns_info[3].name = 'B'
this.btns_info[4].name = 'A' //配置5个按钮的 keycode 默认为 空格 回车 J K
this.btns_info[0].keyCode = opt.keyCodes && opt.keyCodes[0] || 32
this.btns_info[1].keyCode = opt.keyCodes && opt.keyCodes[1] || 13
this.btns_info[2].keyCode = 'macro_key'
this.btns_info[3].keyCode = opt.keyCodes && opt.keyCodes[2] || 74
this.btns_info[4].keyCode = opt.keyCodes && opt.keyCodes[3] || 75 } //初始化 创建虚拟按钮
VirtualNesBtn.prototype.init = function(opt){
var me = this
//创建5个按键
var btn_select = document.createElement('span')
var btn_start = document.createElement('span')
var btn_ab = document.createElement('span')
var btn_b = document.createElement('span')
var btn_a = document.createElement('span') //为5个按键设置css类名
btn_select.classList.add('btn','btn-select')
btn_start.classList.add('btn','btn-start')
btn_ab.classList.add('btn','btn-ab')
btn_b.classList.add('btn','btn-b')
btn_a.classList.add('btn','btn-a') //为5个按键设置id
btn_select.id = me.btns_info[0].id
btn_start.id = me.btns_info[1].id
btn_ab.id = me.btns_info[2].id
btn_b.id = me.btns_info[3].id
btn_a.id = me.btns_info[4].id //为5个按键设置 文本
btn_select.innerText = me.btns_info[0].name
btn_start.innerText = me.btns_info[1].name
btn_ab.innerText = me.btns_info[2].name
btn_b.innerText = me.btns_info[3].name
btn_a.innerText = me.btns_info[4].name //创建容器,并将5个按钮插入其中
var nes_btn_box = document.createElement('div')
nes_btn_box.classList.add('nes_btn_box')
nes_btn_box.appendChild(btn_select)
nes_btn_box.appendChild(btn_start)
nes_btn_box.appendChild(btn_ab)
nes_btn_box.appendChild(btn_b)
nes_btn_box.appendChild(btn_a) //插入到目标容器中
var target = document.querySelector(me.el)
target.appendChild(nes_btn_box) //设置touch事件监听
target.addEventListener('touchstart',(evt) => {
//阻止默认事件,防止快速点击时页面放大
evt.preventDefault() //判断点中的是否是虚拟按钮
var is_nes_btn = me.btns_info.some(function(item){
return item.id === evt.target.id
}) if(is_nes_btn){
//添加高亮
evt.target.classList.add('isTouch')
//处理此次点击
me.handleBtn(evt,'btn_down')
}
}) target.addEventListener('touchend',(evt) => {
//判断点中的是否是虚拟按钮
var is_nes_btn = me.btns_info.some(function(item){
return item.id === evt.target.id
}) if(is_nes_btn){
//移除高亮
evt.target.classList.remove('isTouch')
//处理此次点击
me.handleBtn(evt,'btn_up')
}
})
} //对虚拟按键的id进行判断,返回要绑定的 keyCode
VirtualNesBtn.prototype.getCode = function(id){
var me = this
//1.根据id查到按钮信息在数组中的下标
var index = me.btns_info.findIndex(function(item){
return item.id === id
})
//2.根据下标找到对应的keyCode
return me.btns_info[index].keyCode
} //对按键进行处理
VirtualNesBtn.prototype.handleBtn = function(evt,type){
var me = this
//1.找到keycode
var keyCode = me.getCode(evt.target.id)
//2.根据keycode判是否是宏按键
if(keyCode === 'macro_key'){
//要触发2个按键
var evt_tem = {}
var evt_tem2 = {}
evt_tem.keyCode = me.btns_info[3].keyCode
evt_tem2.keyCode = me.btns_info[4].keyCode
if(type === 'btn_down'){
me.btn_down_fn && me.btn_down_fn(evt_tem)
me.btn_down_fn && me.btn_down_fn(evt_tem2)
}else{
me.btn_up_fn && me.btn_up_fn(evt_tem)
me.btn_up_fn && me.btn_up_fn(evt_tem2)
}
}else{
//添加keyCode属性
evt.keyCode = keyCode
//不是宏按键则执行相应的回调
if(type === 'btn_down'){
me.btn_down_fn && me.btn_down_fn(evt)
}else{
me.btn_up_fn && me.btn_up_fn(evt)
}
}
}

使用示例:

<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NES虚拟按键</title>
<!-- 引入插件的css文件 -->
<link rel="stylesheet" href="./css/nes_btn.css">
<style>
#user_btn_box{
width:250px;
height:250px;
margin: 20px auto;
border: 1px solid greenyellow;
}
</style>
</head>
<body>
<!-- user_btn_box 为用户设置的容器-->
<div id="user_btn_box"></div>
</body>
</html>
<!-- 引入插件 -->
<script src="./js/nes_btn.js"></script>
<script>
function keydown(evt){
console.log('keydown keyCode = ' + evt.keyCode)
}
function keyup(evt){
console.log('keyup keyCode = ' + evt.keyCode)
}
window.onload = function(){
//创建实例
var nesBtn = new VirtualNesBtn({
el:"#user_btn_box", //容器
btn_down_fn:keydown,//虚拟按钮按下时的回调 参数evt
btn_up_fn:keyup,//虚拟按钮弹起时的回调 参数evt
keyCodes:[32,13,74,75] //按顺序分别是 select start b a
}) //实例初始化
nesBtn.init()
}
</script>

执行效果:

移动端NES网页模拟器(1)的更多相关文章

  1. 使用Chrome开发者工具调试Android端内网页(微信,QQ,UC,App内嵌页等)

    使用Chrome开发者工具调试Android端内网页(微信,QQ,UC,App内嵌页等) 前言 移动端页面调试一直是好多朋友头疼的问题,iOS 由于其封闭的特性和整体较高的性能,整体适配相对好做,调试 ...

  2. 移动端 Web 网页调试技巧

    原文出处: 盛瀚钦 本文主要列举了调试本地网页.查看测试环境网页的各种方法,涵盖了PC.iPad.移动端的调试技巧. 本文的不足之处在于,小溪里暂时还没有找到调试位于微信中和安卓各国产浏览器上的网页. ...

  3. 借助FreeHttp为任意移动端web网页添加vConsole调试

        以下介绍在不用修改代码并发布项目的情况下,为我们日常使用的移动web应用(如手机web淘宝)添加vConsole调试工具的方法   vConsole介绍 vConsole是一个轻量.可拓展.针 ...

  4. Appium移动端自动化测试--搭建模拟器和真机测试环境

    详细介绍安装Android Studio及Android SDK.安装Appium Server. 文章目录如下 目录 文章目录如下 模拟器--安装Android Studio及Android SDK ...

  5. HTML5和CSS3 PC端静态网页琐碎知识点

    1.PC端为了兼容IE9以及IE9以下,尽量要使用float进行布局,兼容性好,一般不要用flex进行布局. 2.问起CSS选择器的分类,先说id选择器,类选择器,属性选择器,伪类选择器,伪元素选择器 ...

  6. C#搞个跨平台的桌面NES游戏模拟器

    支持Windows,Mac,Linux    NES模拟器内核源码来自 https://github.com/colinvella/EmuNes   他这边的源码功能很完善了的,支持视频录制,手柄,金 ...

  7. 【转载】小tips: PC端传统网页试试使用Zepto.js进行开发

    Zepto.js设计之初专为移动端,不对一些古董浏览器支持.所以,尺寸很小,压缩后20K多一点,但是,jQuery压缩后,3.*版本要80多K,1.*版本则要90多K,4倍差距. 由于每个页面都会使用 ...

  8. JS判断PC还是移动端打开网页

    最近在做移动端网站,也需兼容PC端.还没找到更好的方法,只能用javascr判断用户是在PC端打开还是移动端打开. JS判断 var isPC = function (){    var userAg ...

  9. 如何实用便捷的在本地真机调试WEB端HTML5网页

    先简单介绍两款常用但需要一定条件或限制的工具 1.如果你能FQ chrome在32版本后就自带了移动端调度工具,可以在Android直接联调,但唯一遗憾的是,在我大天朝要FQ后才能行的通,我自己试了后 ...

  10. 移动端调试 — chrome模拟器基础调试

    打开开发者工具,进入chrome调试状态,点击左上角的手机图标,进入手机模拟器调试状态. 模拟器支持操作: 切换设备类型,模拟网络环境,模拟bar,keyboard弹出状态,横屏状态,更改UserAg ...

随机推荐

  1. 一条 SQL 语句在 MySQL 中是如何执行的?

    本篇文章会分析下一个 SQL 语句在 MySQL 中的执行流程,包括 SQL 的查询在 MySQL 内部会怎么流转,SQL 语句的更新是怎么完成的. 在分析之前我会先带着你看看 MySQL 的基础架构 ...

  2. Java项目笔记(二)

    一.分页待解决的问题 分页是在service层实现的 在controller层和service层同时写了这句代码 PageHelper.startPage(Integer.valueOf(pageNo ...

  3. 使用SourceTree管理仓库代码

    1.首先我们需要下载sourcetree,你可以去官网下载自己需要的版本. 2.安装完毕之后,我们需要获取ssh密钥与github关联上才能使用 按下面的操作开始执行.确定之后,会出现一个字符界面,输 ...

  4. vue3 + vite 分析报告 report == rollup-plugin-visualizer

    安装插件 npm i rollup-plugin-visualizer -D 配置vite.config.js 文件 [加入插件] import { defineConfig } from 'vite ...

  5. 0402-Tensor和Numpy的区别

    0402-Tensor和Numpy的区别 目录 一.tensor数据和ndarray数据相互转换 二.广播法则 pytorch完整教程目录:https://www.cnblogs.com/nickch ...

  6. Vite打包碎片化,如何化解?

    背景 我们在使用 Vite 进行打包时,经常会遇到这个问题:随着业务的展开,版本迭代,页面越来越多,第三方依赖也越来越多,打出来的包也越来越大.如果把页面都进行动态导入,那么凡是几个页面共用的文件都会 ...

  7. 使用 KubeKey 安装部署 Kubernetes 与 Kube-OVN

    作者简介:林瑞超,锐捷网络开发工程师, KubeSphere 社区 contributor, 关注Kube-OVN, Cilium 等容器网络相关技术 背景 KubeKey 是 KubeSphere ...

  8. IntelliJ IDEA 2024激活码(亲测有效,仅供学习和交流)

    资源是从官网购买,仅供学习和交流 激活码链接地址

  9. (待续) 强化学习——如何提升样本效率 ( DeepMind 综述深度强化学习:智能体和人类相似度竟然如此高!)

    强化学习     如何提升样本效率 参考文章: https://news.html5.qq.com/article?ch=901201&tabId=0&tagId=0&docI ...

  10. 《用广义CNOT门产生质数幂维的图态》

    参考文献:Graph states of prime-power dimension from generalized CNOT quantum circuit 主机文件:<2016质数图态.p ...