若是你還不瞭解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還有不少姿式等待你的開啓。