header 好理解,注意其中的“新标签打开图片”相当于过关福利,平常是隐藏的。

之所以内容这么少,是因为主逻辑这一块的 html 代码许多属性都是动态的,所以写死没有价值,需要在 js 里面动态生成与删除,所以基本都移到 js 里面了,这里只要看到几个容器就行。其中 #cut-imgs 是下面游戏的容器。

css

.cut-img { position: absolute; top: 0; left: 0; border: 0; padding: 0; transition: transform .3s linear; box-sizing: border-box; } html, body { height: 100%; margin: 0; } body { display: flex; flex-direction: column; justify-content: center; align-items: center; } header, main, footer { width: 50%; } header { display: flex; justify-content: space-between; } main { position: relative; height: auto; } .game-area { position: relative; height: auto; } #background-img { max-width: 100%; max-height: 100%; vertical-align: top; opacity: 0; } #cut-imgs { position: absolute; top: 0; left: 0; display: flex; flex-wrap: wrap; width: 100%; height: 100%; } button:focus { outline: none; } .selected { border: 1px solid blue; } #download { display: none; }

css 里面注意动画的设置,还有切片图像的处理。

这里相信第一反应下面是把一整张图切 9 份。其实不然,不过是 9 个容器(本例用的是 button)分别展示了不同图片的一部分,然后控制相关的容器即可。

所有容器的位置都是左上角,设置偏移量使其在各个位置上,具体设置方法在 js 里面。

js

const Game = { // 重新开始游戏 restart() { // 清空已有数据,重置按钮 this.reset() const level = this.config.level // 计算 position 的参数 const positionParam = 1 / (level - 1) * 100 const imgUrl = this.config.imgUrl = `https://h5games-dom.oss-cn-hangzhou.aliyuncs.com/puzzle/${~~(Math.random() * 5)}.png` const backgroundImg = document.querySelector('#background-img') backgroundImg.src = imgUrl // 获取样式表 const styleSheet = this.config.imgCutStyle = document.styleSheets[0] // 如果添加过自定义则删除 let firstRule = styleSheet.rules[0] if (firstRule.selectorText === '.custom') styleSheet.deleteRule(0) let scale = 1 / this.config.level * 100 '%' styleSheet.insertRule(`.custom { width: ${scale}; height: ${scale}; background: url(${imgUrl}) no-repeat; background-size: ${this.config.level * 100}%; }`, 0) backgroundImg. = () => { for (let i = 0, j = Math.pow(this.config.level, 2); i < j; i ) { this.config.cutImgsCountArray.push(i) } // DOM字符串 let cutImgsStr = '' this.getInitialSort() this.config.cutImgsCountArray.forEach((num, index) => { // 保存正确的变化,做判断是否获胜的基础 this.config.trueTransforms.push(`translate(${index % level * 100}%, ${~~(index / level) % level * 100}%)`) // 这里设置会变动的 style const transform = `transform: translate(${num % level * 100}%, ${~~(num / level) % level * 100}%);` const backgroundPosition = `background-position: ${index % level * positionParam}% ${~~(index / level) % level * positionParam}%;` // 全部在左上初始位置,设置偏移量即可 cutImgsStr = `<button class="cut-img custom" data-index=${index} ="Game.click(event)" style="${transform backgroundPosition}"></button>` }) document.querySelector('#cut-imgs').innerHTML = cutImgsStr this.instance.cutImgs = document.querySelectorAll('.cut-img') } }, // 点击图片 click(e) { const index = e.target.dataset.index // 第一次点击直接结束 if (this.tool.currentIndex === -1) { this.getCutImg(index).classList.add('selected') this.tool.currentIndex = index return } const oldCutImg = this.getCutImg(this.tool.currentIndex) // 如果点击不是同一个再走逻辑 if (this.tool.currentIndex === index) { this.getCutImg(index).classList.remove('selected') this.tool.currentIndex = -1 } else { const newCutImg = this.getCutImg(index) const [a, b] = [newCutImg.style.transform, oldCutImg.style.transform] oldCutImg.style.transform = a newCutImg.style.transform = b this.tool.currentIndex = -1 setTimeout(() => { download.style.display = 'none' oldCutImg.classList.remove('selected') newCutImg.classList.remove('selected') if (this.checkNoWin()) console.log('NoWin') else { download.style.display = 'block' alert('win') } }, 500); } }, // 获取实例 getCutImg(index) { return this.instance.cutImgs[index] }, // 获取初始的正确排序 getInitialSort() { const cal = arr => { let length = arr.length let reverse = 0 for (let i = 0; i < length - 1; i ) { let n = arr[i] for (let j = i 1; j < length; j ) { let m = arr[j] if (n > m) reverse = 1 } } return reverse } // 数组随机排序 const randomSort = (a, b) => Math.random() > 0.5 ? -1 : 1 // 循环直到获取可还原的排序 while (1) { if (cal(this.config.cutImgsCountArray.sort(randomSort)) % 2 === 0) return } }, // 检查是否还没胜利 checkNoWin() { let cutImgs = this.instance.cutImgs let trueTransforms = this.config.trueTransforms for (let i = 0, j = this.instance.cutImgs.length; i < j; i ) { if (cutImgs[i].style.transform !== trueTransforms[i]) return true } }, // 清空已有数据 reset() { let resetParam = this.resetParam this.config = this.deepCopy(resetParam.config) this.instance = this.deepCopy(resetParam.instance) this.tool = this.deepCopy(resetParam.tool) download.style.display = 'none' }, deepCopy(obj) { return JSON.parse(JSON.stringify(obj)) }, // 打开图片 openImage() { window.open(this.config.imgUrl) }, // 重置时候的初始化参数 resetParam: { // 配置 config: { level: 3, cutImgsCountArray: [], trueTransforms: [], imgCutStyle: {}, imgUrl: '', }, // 实例 instance: { // 所有图片的实例 cutImgs: [], }, // 记录工具 tool: { currentIndex: -1 }, } } Game.restart()

js 就麻烦许多许多了,逻辑和功能匹配,还要用到一些冷门的知识,比如 styleSheets 相关知识,一直用框架,都快忘光了。

说起来简单,就是把前后选中的容器进行 transform 的替换。但是需要注意是基础的业务逻辑:

  1. 第一次和下一次点击的是同一个,那么是要取消选中。
  2. 交换后,需要两个都取消选中。
  3. 重置游戏需要情况上一轮的样式,重新排版。
  4. 游戏过关的业务逻辑。
  5. 游戏难易度配置。
  6. 过关奖励,嘿嘿嘿。
  7. 等等等等。

注意里面有个生成可还原的排序,具体见我之前文章:逆序数,拼图游戏必备知识

具体基本逻辑都在代码里面,相关注释也有加上,喜欢喜欢的小伙伴仔细看看,试试手,练一练。

在这里就不长篇赘述了。

祝你玩的开心。


欢迎关注,如有需要 Web,App,小程序,请留言联系。

查看全文
大家还看了
也许喜欢
更多游戏

Copyright © 2024 妖气游戏网 www.17u1u.com All Rights Reserved