若是你还不了解bui是什么?建议先看看这两篇文章。css
bui.store
是bui的核心一部分,基于DOM的数据驱动。为了让熟悉vuejs更容易上手,接口参照 vuejs 的api设计。恰好看到一个有意思的基于vuejs设计的拼图游戏,因而动手转换,把整个开发过程记录下来,并作了移动端适配,优化,不费吹灰之力,拼图源码的创意归原做者全部。html
在线拼图玩一玩vue
该拼图游戏项目来源于:https://github.com/luozhihao/...git
App.vue 核心代码:具体的实现能够查看源码,这里只保留基本的结构。es6
<template> <div class="box"> <ul class="puzzle-wrap"> <li :class="{'puzzle': true, 'puzzle-empty': !puzzle}" v-for="puzzle in puzzles" v-text="puzzle" @click="moveFn($index)" ></li> </ul> <button class="btn btn-warning btn-block btn-reset" @click="render">重置游戏</button> </div> </template> <script> export default { data () { return { puzzles: [] } }, methods: { // 重置渲染 render () { //部分省略 this.puzzles = puzzleArr this.puzzles.push('') }, // 点击方块 moveFn (index) { //省略 }, // 校验是否过关 passFn () { //省略 } }, mounted () { this.render() } } </script>
样式部分代码暂时去掉
bui-puzzle
# 建立工程 buijs create bui-puzzle
若是没有安装
buijs构建工具
, 能够直接
点击下载单页工程包
# 进入文件夹 cd bui-puzzle # 安装依赖 npm install # 起服务预览效果 npm run dev
这些是工程化的处理,这个游戏只有一个页面,也没有数据请求那些,因此直接打开 index.html 就能够了。
了解bui的都知道,单页工程里面,打开index.html
默认会加载pages/main/main.html
,pages/main/main.js
模板,因此咱们接下来要更改这2个文件,一个是模板,一个是逻辑。
main.htmlgithub
咱们把它改为这样,去掉顶部图标,修改标题,去掉footer标签,把vuejs版的内容复制过来,放在main标签里面,作如下修改。web
<div class="bui-page"> <header class="bui-bar"> <div class="bui-bar-left"> </div> <div class="bui-bar-main">BUI拼图游戏-数据驱动</div> <div class="bui-bar-right"> </div> </header> <main> <div class="box"> <ul class="puzzle-wrap" b-template="page.tplGame(page.puzzles)"></ul> <button class="bui-btn" b-click="page.render">重置游戏</button> </div> </main> </div>
说明:bui-page是标准的页面结构,main标签是滚动的内容容器;b-
开头的是bui.store
的行为属性,b-template
为模板,b-click
为事件。值为page.xxx
page能够理解为自定义的做用域,初始化的时候的 scope参数。
main.js 输入bui-store
,生成初始化,须要安装 bui-fast
(vscode直接搜索插件bui-fast
安装),把刚刚导出的模块里面的方法,复制到 methods
, templates
为 main.html
指向的模板名tplGame
。npm
loader.define(function(require, exports, module) { // 初始化数据行为存储, this 指向当前模块, bs 为 Behavior Store 简称。 let bs = bui.store({ //el: ".bui-page", scope: "page", data: { puzzles: [] }, methods: { // 重置渲染 render() { //部分省略 puzzleArr.push(''); // 这里不能直接采用赋值,须要使用数组提供的方法才能触发视图更新 this.puzzles.$replace(puzzleArr) }, // 点击方块 moveFn(index) { //省略 }, // 校验是否过关 passFn() { //省略 } }, watch: {}, computed: {}, templates: { tplGame(data) { // b-template指向当前模板 var html = ""; // 返回结构 return html; } }, mounted: function() { // 数据解析后执行 this.render(); } }) })
参数说明bootstrap
.bui-page
,使用标准结构则不用管这个参数;page
, 这个能够理解为做用域,写模板的时候,须要以这样 page.xxx
指向对应的方法或者数据;b-template
指向的模板方法名,须要返回对应的结构;其它基本跟vuejs保持一致。初始化必须写在 loader.define
里面。segmentfault
main.html
<style> @import url('css/bootstrap.min.css'); body { font-family: Arial, "Microsoft YaHei"; } .box { width: 400px; margin: 50px auto 0; } .puzzle-wrap { width: 400px; height: 400px; margin-bottom: 40px; padding: 0; background: #ccc; list-style: none; } .puzzle { float: left; width: 100px; height: 100px; font-size: 20px; background: #f90; text-align: center; line-height: 100px; border: 1px solid #ccc; box-shadow: 1px 1px 4px; text-shadow: 1px 1px 1px #B9B4B4; cursor: pointer; } .puzzle-empty { background: #ccc; box-shadow: inset 2px 2px 18px; } .btn-reset { box-shadow: inset 2px 2px 18px; } </style> <div class="bui-page"> <header class="bui-bar"> <div class="bui-bar-left"> </div> <div class="bui-bar-main">BUI拼图游戏-数据驱动</div> <div class="bui-bar-right"> </div> </header> <main> <div class="box"> <ul class="puzzle-wrap bui-fluid-4" b-template="page.tplGame(page.puzzles)"></ul> <button class="bui-btn warning round" b-click="page.render">重置游戏</button> </div> </main> </div>
main.js
/** * 拼图游戏 * 默认模块名: main */ loader.define(function(require, exports, module) { // 初始化数据行为存储 var bs = bui.store({ scope: "page", data: { puzzles: [] }, methods: { // 重置渲染 render:function() { let puzzleArr = [], i = 1 // 生成包含1 ~ 15数字的数组 for (i; i < 16; i++) { puzzleArr.push(i) } // 随机打乱数组 puzzleArr = puzzleArr.sort(() => { return Math.random() - 0.5 }); // 页面显示,前面声明是数组之后,不能使用赋值监听到变动,先处理再替换,这样只触发一次视图变动 puzzleArr.push(''); this.puzzles.$replace(puzzleArr) }, // 点击方块 moveFn:function (index) { // 获取点击位置及其上下左右的值 let curNum = this.puzzles[index], leftNum = this.puzzles[index - 1], rightNum = this.puzzles[index + 1], topNum = this.puzzles[index - 4], bottomNum = this.puzzles[index + 4] // 和为空的位置交换数值 if (leftNum === '' && index % 4) { this.puzzles.$set(index - 1, curNum) this.puzzles.$set(index, '') } else if (rightNum === '' && 3 !== index % 4) { this.puzzles.$set(index + 1, curNum) this.puzzles.$set(index, '') } else if (topNum === '') { this.puzzles.$set(index - 4, curNum) this.puzzles.$set(index, '') } else if (bottomNum === '') { this.puzzles.$set(index + 4, curNum) this.puzzles.$set(index, '') } this.passFn() }, // 校验是否过关 passFn:function () { if (this.$data.puzzles[15] === '') { const newPuzzles = this.puzzles.slice(0, 15) const isPass = newPuzzles.every((e, i) => e === i + 1) if (isPass) { alert ('恭喜,闯关成功!') } } } }, watch: {}, computed: {}, templates: { tplGame: function (data) { var html = ""; data.forEach(function (puzzle,index) { var hasEmpty = !puzzle ? "puzzle-empty" : ""; html +=`<li class="puzzle ${hasEmpty}" b-click="page.moveFn($index)">${puzzle}</li>` }) return html; } }, mounted: function(){ // 数据解析后执行 this.render(); } }) })
这里咱们的方法定义改成了 es5的写法,移动端有些对es6并不友好。
模板对比
模块脚本对比
经过比对 vuejs
版跟 bui.store
版本,你会发现,里面的业务方法几乎不用改变,重点在vuejs 模板引擎转换成 bui的模板的思路转换,bui模板里面使用 es6模板引擎,每次数据改变都会把模板从新渲染一遍,无需经过虚拟Dom去比对改变的位置。
bui.store
是基于真实Dom的数据驱动。当数据改变之后,会找到对应的选择器作对应的变动。上面的例子若是你在 tplGame
模板方法里面输出,你会发现,每次点击一个方块,会有2次触发,意味着有2次Dom渲染,这个应该避免。
优化后的 main.js
loader.define(function(require, exports, module) { // 初始化数据行为存储 let bs = bui.store({ scope: "page", data: { puzzles: [] }, methods: { // 重置渲染 render: function() { let puzzleArr = [], i = 1 // 生成包含1 ~ 15数字的数组 for (i; i < 16; i++) { puzzleArr.push(i) } // 随机打乱数组 puzzleArr = puzzleArr.sort(() => { return Math.random() - 0.5 }); // 页面显示,前面声明是数组之后,不能使用赋值监听到变动,先处理再替换,这样只触发一次视图变动 puzzleArr.push(''); // 这里若是直接赋值不会触发视图更新 // this.puzzles = puzzleArr; this.puzzles.$replace(puzzleArr); }, // 点击方块 moveFn: function(index) { // 获取点击位置及其上下左右的值 let curNum = this.$data.puzzles[index], leftNum = this.$data.puzzles[index - 1], rightNum = this.$data.puzzles[index + 1], topNum = this.$data.puzzles[index - 4], bottomNum = this.$data.puzzles[index + 4] // 和为空的位置交换数值 if (leftNum === '' && index % 4) { // 只修改数据,不触发视图 this.$data.puzzles[index - 1] = curNum; this.$data.puzzles[index] = ''; // 触发一次视图 this.puzzles.$replace(this.$data.puzzles); } else if (rightNum === '' && 3 !== index % 4) { // 只修改数据,不触发视图 this.$data.puzzles[index + 1] = curNum; this.$data.puzzles[index] = ''; // 触发一次视图 this.puzzles.$replace(this.$data.puzzles); } else if (topNum === '') { // 只修改数据,不触发视图 this.$data.puzzles[index - 4] = curNum; this.$data.puzzles[index] = ''; // 触发一次视图 this.puzzles.$replace(this.$data.puzzles); } else if (bottomNum === '') { // 只修改数据,不触发视图 this.$data.puzzles[index + 4] = curNum; this.$data.puzzles[index] = ''; // 触发一次视图 this.puzzles.$replace(this.$data.puzzles); } this.passFn() }, // 校验是否过关 passFn: function() { if (this.$data.puzzles[15] === '') { const newPuzzles = this.$data.puzzles.slice(0, 15) const isPass = newPuzzles.every((e, i) => e === i + 1) if (isPass) { bui.alert('恭喜,闯关成功!') } } } }, watch: {}, computed: {}, templates: { tplGame: function(data) { var html = ""; data.forEach(function(puzzle, index) { var hasEmpty = !puzzle ? "puzzle-empty" : ""; html += `<li class="puzzle ${hasEmpty}" b-click="page.moveFn($index)">${puzzle}</li>` }) return html; } }, mounted: function() { // 数据解析后执行 this.render(); } }) })
优化说明
this.puzzles
改为了 this.$data.puzzles
,这里数据的访问只有一层,因此没有出现问题;this.$data.puzzles
;this.abc = 123
的时候, this.$data.abc === 123
;this.$data.abc = 123;
this.abc
仍是等于原来的值;this.abc
会触发视图更新,this.$data.abc
则不会。例子:
这里修改一次数据,改为就会触发一次视图更新,形成2次页面渲染。
this.puzzles.$set(index + 1, curNum); this.puzzles.$set(index, '');
优化:
// 只修改数据,不触发视图 this.$data.puzzles[index + 4] = curNum; this.$data.puzzles[index] = ''; // 最后触发一次视图 this.puzzles.$replace(this.$data.puzzles);
这个效果在PC效果还不错,但到了手机端就成了这样,手机没法正常操做。
优化:去除引入 bootstrap样式, 按照BUI的750设计规范,把效果图转成 rem 单位。1rem=100px
,宽度只要不大于750px就不会有横向滚动条。把本来的float布局也改成 bui-fluid-4
列布局,里面只须要定义好高度就能够了。
<style type="text/css"> .box { width: 6.8rem; margin: .4rem auto 0; } .puzzle-wrap { width: 100%; height: 6.8rem; margin-bottom: .4rem; padding: 0; background: #ccc; } .puzzle { width: 1.7rem; height: 1.7rem; font-size: .4rem; background: #f90; text-align: center; line-height: 1.7rem; border: 1px solid #ccc; box-shadow: 1px 1px 4px; text-shadow: 1px 1px 1px #B9B4B4; cursor: pointer; } .puzzle-empty { background: #ccc; box-shadow: inset 2px 2px 18px; } </style> <div class="bui-page"> <header class="bui-bar"> <div class="bui-bar-left"> </div> <div class="bui-bar-main">BUI拼图游戏-数据驱动</div> <div class="bui-bar-right"> </div> </header> <main> <div class="box"> <ul class="puzzle-wrap bui-fluid-4" b-template="page.tplGame(page.puzzles)"></ul> <button class="bui-btn warning round" b-click="page.render">重置游戏</button> </div> </main> </div>
优化后的适配效果图:
iPhone5 | iPhone678 | iPhone678 Plus |
---|---|---|
![]() |
![]() |
![]() |
执行编译压缩,把工程生成 dist
目录,这个就是用来发布的文件夹,代码会执行转换编译成es5以及把js压缩。
npm run build
该源码放在bui-puzzle。喜欢就给颗星星吧。^_^
谢谢阅读!欢迎关注BUI Webapp专栏。BUI还有不少姿式等待你的开启。