使用 jQuery(逻辑无关框架,可复用)
HTML 模板结构
<div class="main play-page"><div class="main-content">// 用于游戏计时<div class="count-time prompt"><i class="prompt-icon"></i><span class="prompt-text">用时 <span class="j-time-number">0</span> 秒</span></div>// 拼图父元素<div class="puzzle-box">// 拼图原图,用于拼图九宫格渲染未完成前展示<img class="puzzle-preview" src="" alt="预览图">// 拼图容器<ul class="puzzle-content" style="display: none;">// 九宫格<li class="debris"><img src=""></li><li class="debris"><img src=""></li><li class="debris"><img src=""></li><li class="debris"><img src=""></li><li class="debris"><img src=""></li><li class="debris"><img src=""></li><li class="debris"><img src=""></li><li class="debris"><img src=""></li><li class="debris"><img src=""></li></ul></div>// 利用 canvas 生成九宫格<canvas style="display: none;" id="puzzle-board" style="width:318px; height:318px;"></canvas></div><div class="prompt"><i class="prompt-icon"></i><span class="prompt-text">点击图片交换位置</span></div><div class="quit-game btn j-quit-game">放弃挑战</div></div>
CSS 样式
.prompt {display: flex;justify-content: center;align-items: center;margin-top: .28rem;&-icon {display: inline-block;margin-right: .06rem;width: .22rem;height: .22rem;background: url(https://img1.dxycdn.com/2020/0424/126/3409583174073156416-2.png) no-repeat center center;background-size: contain;}&-text {font-size:.24rem;font-family:PingFangSC-Regular,PingFang SC;font-weight:400;color:rgba(255,224,189,1);}}.play-page {position: relative;width: 100%;height: 100%;overflow: hidden;background: url(https://img1.dxycdn.com/2020/0424/066/3409601700414728802-2.png) no-repeat center 0;background-size: contain;background-color: #9d2729;.main-content {margin: 2.28rem auto 0;border-radius: .12rem;width: 6.86rem;height: 7.24rem;background:rgba(255,232,232,1);.count-time {padding: .1rem 0;.prompt-icon {width: .24rem;height: .24rem;background-image: url(https://img1.dxycdn.com/2020/0424/785/3409601623105175531-2.png);}.prompt-text {font-size: .28rem;color:rgba(51,51,51,1);}}.puzzle-box {margin: 0 auto;width: 318px;height: 318px;.puzzle-content {position: relative;display: flex;flex-wrap: wrap;width: 100%;height: 100%;.debris {position: absolute;border-radius: .08rem;border: .02rem solid transparent;width: 106px;height: 106px;transition: left 0.5s ease, top 0.5s ease;&:nth-of-type(1) {top: 0;left: 0;}&:nth-of-type(2) {top: 0;left: 106px;}&:nth-of-type(3) {top: 0;left: 212px;}&:nth-of-type(4) {top: 106px;left: 0;}&:nth-of-type(5) {top: 106px;left: 106px;}&:nth-of-type(6) {top: 106px;left: 212px;}&:nth-of-type(7) {top:212px;left: 0;}&:nth-of-type(8) {top: 212px;left: 106px;}&:nth-of-type(9) {top: 212px;left: 212px;}&.active{z-index: 99;border-color: #9d2628;}img {border-radius: 4px;width: 100%;height: 100%;}}}}}.quit-game {margin: .53rem auto 0;border-radius: 1rem;border: .02rem solid rgba(199,164,105,1);width: 3.4rem;height: .8rem;line-height: .8rem;text-align: center;font-size: .32rem;font-family: PingFangSC-Medium,PingFang SC;font-weight: 500;color: rgba(207,171,113,1);letter-spacing: .02rem;}}
JavaScript 游戏逻辑
import '@/css/play.less'import $ from 'jquery'const H5App = {// 获取 cavans DOM 对象puzzleBoardCanvas: null,// 2d 绘画上下文ctx: null,// 拼图正确顺序passArr: [1, 2, 3, 4, 5, 6, 7, 8, 9],// 保存九宫格碎片打乱后的顺序imgArr: [1, 2, 3, 4, 5, 6, 7, 8, 9],// 经过碎片图位置交换后的最新顺序resArr: [],// 原图地址originImgUrl: '',// 九宫格拼图元素集合debrisSet: null,// 位置交换元素中转站transferStation: [],// 游戏完成结束回调gamePassedCallback: () => {},// 放弃挑战回调quitGameCallback: () => {},// 游戏中回调inTheGame: () => {},// 游戏时间gameTime: 0,// 游戏计时定时器gameTimer: null,/*** 给九宫格碎片图绑定 touch 事件*/bind () {const self = thisself.debrisSet.on('click', function (evt) {evt.preventDefault()// 处理当前碎片的信息const $this = $(this)$this.addClass('active')// 当前碎片信息对象 => 下标 && 位置信息const currentDebrisMsg = {}// indexconst selfIndex = $this.index()currentDebrisMsg.index = selfIndex// positioncurrentDebrisMsg.position = {x: $this.position().left,y: $this.position().top}// 将信息对象推入中转站数组中self.transferStation.push(currentDebrisMsg)// 当中转站有两个碎片时,开始交换位置if (self.transferStation.length === 2) {/*** 交换数据信息 position 信息互换* origin* [* 0: {index: 0, position: {x: 0, y: 0}},* 1: {index: 4, position: {x: 106, y: 106}}* ]* transfer* [* 0: {index: 0, position: {x: 106, y: 106}},* 1: {index: 4, position: {x: 0, y: 0}}* ]*/// 调换位置 position 信息let tempPosition = {}tempPosition = self.transferStation[0].positionself.transferStation[0].position = self.transferStation[1].positionself.transferStation[1].position = tempPosition// 排序 imgArrlet tempSeq = ''tempSeq = self.imgArr[self.transferStation[0].index]self.imgArr[self.transferStation[0].index] = self.imgArr[self.transferStation[1].index]self.imgArr[self.transferStation[1].index] = tempSeq// 绝对定位,css 交换位置// 并且重置激活状态的碎片self.transferStation.map((item) => {$(self.debrisSet[item.index]).css({top: item.position.y,left: item.position.x}).removeClass('active')})// 清空中转站,重新开始self.transferStation.length = 0console.log(self.imgArr)// 判断是否通关self.checkPass()}})$('.j-quit-game').on('click', function (evt) {evt.preventDefault()clearInterval(self.gameTimer)self.quitGameCallback()})},/*** 游戏开始*/gameStart () {this.gameTimer = setInterval(() => {this.inTheGame(this.gameTime++)}, 1000)},/*** 判断拼图游戏是否通关*/checkPass () {if (this.imgArr.join('') === this.passArr.join('')) {clearInterval(this.gameTimer)this.gamePassedCallback(this.gameTime - 1)}},/*** 九宫格渲染完成*/renderfinished () {$('.puzzle-preview').css('display', 'none')$('.puzzle-content').css('display', 'block')// 游戏计时开始this.gameStart()},/*** 打乱九宫格碎片图*/randomImage () {this.imgArr.sort(function (a, b) {return Math.random() - Math.random()})},/*** 切割原图形成碎片图* @param {object} img - 图片元素 dom 对象*/splitImage (img) {this.randomImage()let index = 1for (let i = 0; i < 3; i++) {for (let j = 0; j < 3; j++) {this.ctx.drawImage(img, 318 * j, 318 * i, 318, 636, 0, 0, 318, 318)this.debrisSet.eq(this.imgArr[index - 1] - 1).find('img').attr('src', this.puzzleBoardCanvas.toDataURL('image/jpeg'))index++}}// 九宫格绘制完成this.renderfinished()},/*** 绘制九宫格图片*/render () {const self = thisconst img = new Image()img.crossOrigin = 'anonymous'img.onload = function () {self.splitImage(img)}img.src = this.originImgUrl},/*** 数据初始化* @param {object} opts - 九宫格游戏初始化配置项* --> @param {object} puzzleBoardCanvas - canvas 容器 dom 对象* --> @param {string} originImgUrl - 九宫格原图地址* --> @param {array} debrisSet - 九宫格格子碎片 dom 数组集合*/init (opts) {if (opts.puzzleBoardCanvas) {this.puzzleBoardCanvas = opts.puzzleBoardCanvas} else {console.error('puzzleBoardCanvas 参数错误')}if (opts.originImgUrl !== '') {this.originImgUrl = opts.originImgUrl$('.puzzle-preview').attr('src', this.originImgUrl)} else {console.error('originImgUrl 参数错误')}if (opts.debrisSet && opts.debrisSet.length === 9) {this.debrisSet = opts.debrisSet} else {console.error('debrisSet 参数错误')}if (opts.quitGameCallback && typeof opts.quitGameCallback === 'function') {this.quitGameCallback = opts.quitGameCallback} else {console.error('quitGameCallback 参数错误')}if (opts.inTheGame && typeof opts.inTheGame === 'function') {this.inTheGame = opts.inTheGame} else {console.error('inTheGame 参数错误')}if (opts.gamePassedCallback && typeof opts.gamePassedCallback === 'function') {this.gamePassedCallback = opts.gamePassedCallback} else {console.error('gamePassedCallback 参数错误')}},/*** H5 游戏启动*/startDraw (opts) {// 九宫格配置初始化this.init(opts)// 判断浏览器是否支持 canvasif (this.puzzleBoardCanvas.getContext) {// 访问 2d 绘画上下文this.ctx = this.puzzleBoardCanvas.getContext('2d')this.render()} else {alert('您的浏览器不支持 Canvas,请换一个浏览器再尝试')}// 绑定事件this.bind()}}const debrisSet = $('.puzzle-box .puzzle-content .debris')const originImgUrl = 'https://img1.dxycdn.com/2020/0423/836/3409374155194655434-2.jpeg'const puzzleBoardCanvas = document.getElementById('puzzle-board')H5App.startDraw({puzzleBoardCanvas,originImgUrl,debrisSet,inTheGame: function (countTime) {$('.j-time-number').text(countTime)},quitGameCallback: function () {window.open('/index', '_self')},gamePassedCallback: function (countTime) {setTimeout(() => {window.open(`/survey?time=${countTime}`, '_self')}, 1500)}})

