分类 JavaScript 下的文章 - 青蓝鱼的博客-没有bug的代码是不完美的
分类 JavaScript 下的文章 - 青蓝鱼的博客-没有bug的代码是不完美的
终于找到这篇文章了,感谢作者的分享!
https://fishfive.top/index.php/archives/29/
终于找到这篇文章了,感谢作者的分享!
受益匪浅,感谢博主。
该回复疑似异常,已被系统拦截!
1
111
666
# 图片回复
666
学到了
666
hello word
首页
关于
?
归档
留言
统计
导航
更多
github
友链
推荐
百度
搜 索
1
Nginx-Quic重新编译Nginx支持HTTP3
356 阅读
2
Centos7和Centos8网卡配置
269 阅读
3
六种好看的css按钮效果
189 阅读
4
node.js简单的web服务demo
159 阅读
5
JavaScript实现静态图片局部流动效果
156 阅读
默认分类
html
css
JavaScript
React
Vue
Git
centos
node.js
php
nginx
http
登录
搜 索
https://fishfive.top
累计撰写
27
篇文章
累计收到
16
条评论
首页
栏目
默认分类
html
css
JavaScript
React
Vue
Git
centos
node.js
php
nginx
http
页面
关于
归档
留言
统计
导航
github
友链
推荐
百度
用户登录
登录
找到
4
篇与
JavaScript
相关的结果
2022-12-02
网站添加 pwa 支持
什么是PWA(渐进式 Web 应用)PWA(Progressive Web Apps,渐进式 Web 应用)运用现代的 Web API 以及传统的渐进式增强策略来创建跨平台 Web 应用程序。这些应用无处不在、功能丰富,使其具有与原生应用相同的用户体验优势。PWA 是可被发现、易安装、可链接、独立于网络、渐进式、可重用、响应性和安全的。简单理解,PWA可以将Web应用转化成能够在多平台设备上使用的原生应用程序并且具有和原生应用接近的交互体验,无需下载直接安装。注意:PWA并不是一种单独的技术,而是多项Web技术集成的Web App,其核心技术包括App Manifest、Service Worker、Web Push等。pwa优势它比原生应用更轻量,但是却比现有的 Web APP 的功能更加丰富。最大也是最关键的区别是它能够脱离浏览器的「束缚」(虽然依然是基于浏览器的技术),能够把 PWA 网站添加到你的桌面上,不管是 PC 操作系统还是手机操作系统,类似于一个原生应用一样,并且拥有媲美原生应用的体验。而微信支付宝等小程序更封闭,是 Web 的子集。快速为网站配置PWA开启HTTPS,并且强制HTTPS根目录上传 Manifest.JSON 并修改内容根目录上传 Service-Worker.JS注册Service Worker配置ManifestManifest是一份用JSON记录网站信息的清单,其中包含了应用的标题、图标的大小与路径、加载页的背景颜色等,只有编写了这份清单浏览器才能正确处理Web App信息。在编写Manifest时会发现有不同拓展名,如manifest.webapp、manifest.json以下是我编写的一份Manifest示例:{ "background_color": "#fff", //Web App启动时的背景颜色,可以使用预设的一些颜色名字如red,blue等,或者直接使用HEX颜色值如#66ccff(天依蓝) "description": "Describe your site.", //Web App的描述,这里的内容将会在安装时展现 "display": "fullscreen", //Web App展示的方式,可选的值有fullscreen,standalone,minimal-ui,browser等,具体含义请见文末拓展阅读 "icons": [ { "src": "icon.png", "sizes": "192x192", "type": "image/png" } ], //这里举例了应用图片的配置,可以配置多个图标! "name": "Awesome fox pictures", //应用的名称,在安装时展示 "short_name": "Foxes", //缩略名,一般在添加到桌面之后会展示这个名字 "start_url": "/" //启动后进入的 }然后将本文件保存为manifest.json并放置在网站根目录,并在网站标签内添加以下内容:<link rel="manifest" href="manifest.webmanifest">Safari浏览器对PWA支持并不完善,如果需要正确的展示PWA应用,需要将以下代码根据实际情况调整后添加在标签内:<link rel="shortcut icon" href="favicon.ico"> <link rel="shortcut icon" href="src to icon" type="image/x-icon" /> <link rel="apple-touch-icon" href="src to icon" /> <meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-title" content="Web App Name">配置Service Worker接下来是Service Worker的代码,将其命名为sw.js并保存。// sw.js var cacheStorageKey = 'poem-0.0.2' var cacheList = [] self.addEventListener('install', e => { // install 事件,它发生在浏览器安装并注册 Service Worker 时 // e.waitUtil 用于在安装成功之前执行一些预装逻辑 e.waitUntil( caches.open(cacheStorageKey) .then(cache => cache.addAll(cacheList)) .then(() => self.skipWaiting()) ) }) self.addEventListener('fetch', function(e) { e.respondWith( caches.match(e.request).then(function(response) { if (response != null) { return response } return fetch(e.request.url) }) ) }) self.addEventListener('activated', function(e) { e.waitUntil( // 获取所有cache名称 caches.keys().then(cacheNames => { return Promise.all( // 获取所有不同于当前版本名称cache下的内容 cacheNames.filter(cacheNames => { return cacheNames !== cacheStorageKey }).map(cacheNames => { return caches.delete(cacheNames) }) ) }).then(() => { return self.clients.claim() }) ) })sw.js 的内容基本是从网上拷贝的。这个文件会运行在 service worker 线程中,并且会拦截各种网络请求。文件中监听的 fetch 事件回调就是对请求进行的处理。我这里会尝试从缓存获取,如果缓存没有才发起请求。在一开始,我将跟路径“/”添加到了 cacheList 中。其结果是首页被缓存,导致后续发版无法更新。还好当时是本地测试,在浏览器开发工具 application 面板清除缓存并将 cacheList 置空后,问题迎刃而解。当然,这种做法并不好,实际上可以通过检测缓存版本解决。不过,算了,我只是需要将网站添加到桌面。对于只需要添加桌面图标,不需要缓存内容,我找到了更简单的版本。const cacheName = 'kodcloud'; const staticAssets = []; self.addEventListener('install', async e => { }); self.addEventListener('activate', e => { self.clients.claim(); }); self.addEventListener('fetch', async e => { });注册Service Worker首先编写index.js,这部分内容也可以直接编写在网页的<script>标签中。 if(navigator.serviceWorker != null){ navigator.serviceWorker.register('sw.js',) .then(function(registartion){ //注册成功 console.log('支持sw:',registartion.scope) }).catch(function (err){ console.log('fail') }); }至此,大功告成。
2022年12月02日
85 阅读
0 评论
0 点赞
2022-11-08
JavaScript实现静态图片局部流动效果
背景如果你有玩过《王者荣耀》、《阴阳师》 等手游,一定注意到过它的启动动画、皮肤立绘卡片等场景,经常采用静态底图加局部液态流动效果的简单动画,这些流动动画可能出现在缓缓流动的水流 、迎风飘动的旗帜 、游戏角色衣袖 、随着时间缓动的云、雨、雾天气效果 等。这种过渡效果不仅节省了开发全量动画的成本,而且使得游戏画面更加热血、冒险、奥德赛、高级,也更加容易吸引玩家氪金 。本文使用前端开发技术,结合 SVG 和 CSS 来实现类似的液化流动效果。本文包含的知识点主要包括:mask-image 遮罩、feTurbulence 和 feDisplacementMap 滤镜、filter 属性、canvas 绘制方法、TimelineMax 动画以及input[type=file] 本地图片资源加载等。效果先来看看实现效果,下面几个示例以及文章 Banner 图都是应用了由本文内容生成的液态流动动画效果。由于GIF 图压缩比较严重,动画效果看起来不是很流畅。雾气扩散 塞尔达传说:旷野之息衣袖飘动 貂蝉:猫影幻舞湖光波动实现页面主要由2部分构成,顶部用于加载图片 ,并且可以通过按住鼠标划动的方式绘制热点路径,给图片添加流动效果;底部是控制区域,点击按钮清除画布,可以清除绘制的流动动画效果、点击按钮切换图片可以加载本地的图片。HTML 页面结构\#sketch 元素主要是用于绘制和加载流动效果热点图的画板;\#button_container 是页面底部的按钮控制区域;svg 元素用于利用其 filter 滤镜实现液态流动动画效果,包括 feTurbulence 和 feDisplacementMap 滤镜。<main id="sketch"> <canvas id="canvas" data-img=""></canvas> <div class="mask"> <div id="maskInner" class="mask-inner"></div> </div> </main> <section class="button_container"> <button class="button">清除画布</button> <button class="button"><input class="input" type="file" id="upload">上传图片</button> </section> <svg> <filter id="heat" filterUnits="objectBoundingBox" x="0" y="0" width="100%" height="100%"> <feTurbulence id="heatturb" type="fractalNoise" numOctaves="1" seed="2" /> <feDisplacementMap xChannelSelector="G" yChannelSelector="B" scale="22" in="SourceGraphic" /> </filter> </svg> feTurbulence 和 feDisplacementMapfeTurbulence:滤镜利用 Perlin 噪声函数创建了一个图像,利用它可以实现人造纹理比如说云纹、大理石纹等模拟滤镜效果。feDisplacementMap:映射置换滤镜,该滤镜用来自图像中从 in2 到空间的像素值置换图像从 in 到空间的像素值。即它可以改变元素和图形的像素位置,通过遍历原图形的所有像素点,feDisplacementMap 重新映射替换一个新的位置,形成一个新的图形。该滤镜在业界的主流应用是对图形进行形变,扭曲,液化。CSS 样式接着看看样式的实现,main 元素作为主容器并将主图案作为背景图片;canvas 作为画布占据 100% 的空间位置;.mask 和 .mask-inner 用于生成如下图所示热点路径与背景图相溶的效果,这种效果是借助 mask-image 实现的。最后,为了生成动态流动效果,.mask-inner 通过 filter: url(#heat) 将前面生成的 svg 作为滤镜来源,后续即将在 JavaScript 中通过不间断修改 svg 滤镜的属性,来生成液态流动动画。main { position: relative; background-image: url('bg.jpg'); background-size: cover; background-position: 100% 50%; } canvas { opacity: 0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; } .mask { display: none; position: absolute; top: 0; left: 0; width: 100%; height: 100%; mask-mode: luminance; mask-size: 100% 100%; backdrop-filter: hard-light; mask-image: url('mask.png'); } .mask-inner { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: url('bg.jpg') 0% 0% repeat; background-size: cover; background-position: 100% 50%; filter: url(#heat); mask-image: url('mask.png') } mask-imagemask-image CSS 属性用于设置元素上遮罩层的图像。语法:// 默认值,透明的黑色图像层,也就是没有遮罩层。 mask-image: none; // <mask-source><mask>或CSS图像的url的值 mask-image: url(masks.svg#mask1); // <image> 图片作为遮罩层 mask-image: linear-gradient(rgba(0, 0, 0, 1.0), transparent); mask-image: image(url(mask.png), skyblue); // 多个值 mask-image: image(url(mask.png), skyblue), linear-gradient(rgba(0, 0, 0, 1.0), transparent); // 全局值 mask-image: inherit; mask-image: initial; mask-image: unset; 兼容性:JavaScript 方法① 绘制热点图监听鼠标移动和点击事件,在 canvas 上绘制波动路径热点。var canvas = document.getElementById('canvas'); var ctx = canvas.getContext('2d'); var sketch = document.getElementById('sketch'); var sketchStyle = window.getComputedStyle(sketch); var mouse = ; canvas.width = parseInt(sketchStyle.getPropertyValue('width')); canvas.height = parseInt(sketchStyle.getPropertyValue('height')); canvas.addEventListener('mousemove', e => { mouse.x = e.pageX - canvas.getBoundingClientRect().left; mouse.y = e.pageY - canvas.getBoundingClientRect().top; }, false); ctx.lineWidth = 40; ctx.lineJoin = 'round'; ctx.lineCap = 'round'; ctx.strokeStyle = 'black'; canvas.addEventListener('mousedown', () => { ctx.beginPath(); ctx.moveTo(mouse.x, mouse.y); canvas.addEventListener('mousemove', onPaint, false); }, false); canvas.addEventListener('mouseup', () => { canvas.removeEventListener('mousemove', onPaint, false); }, false); var onPaint = () => { ctx.lineTo(mouse.x, mouse.y); ctx.stroke(); var url = canvas.toDataURL(); document.querySelectorAll('div').forEach(item => { item.style.cssText += ` display: initial; -webkit-mask-image: url($); mask-image: url($); `; }); }; 绘制完成后,可以在页面中右键保存生成的波动路径热点图,直接将绘制满意的热点图放到 CSS 中,就能给喜欢的图片添加局部波动效果了,下面这张图片就是本示例页面使用的波动的热点路径图。② 生成动画为了生成实时更新的波动效果,本文使用了 TweenMax 来通过改变 feTurbulence 的 baseFrequency 属性值来实现,使用其他动画库或使用 requestAnimationFrame 也是可以实现相同的功能。feTurb = document.querySelector('#heatturb'); var timeline = new TimelineMax({ repeat: -1, yoyo: true }), timeline.add( new TweenMax.to(feTurb, 8, { onUpdate: () => { var bfX = this.progress() * 0.01 + 0.025, bfY = this.progress() * 0.003 + 0.01, bfStr = bfX.toString() + ' ' + bfY.toString(); feTurb.setAttribute('baseFrequency', bfStr); } }), 0); ③ 清除画布点击清除画布按钮,可以清空已经绘制的波动路径,主要是通过清除页面元素 mask-image 的属性值以及清 canvas 画布来实现的。function clear() { document.querySelectorAll('div').forEach(item => { item.style.cssText += ` display: none; -webkit-mask-image: none; mask-image: none; `; }); } document.querySelectorAll('.button').forEach(item => { item.addEventListener('click', () => { ctx.clearRect(0, 0, canvas.width, canvas.height); clear(); }) }); ④ 切换图片点击切换图片,可以加载本地的一张图片作为绘制底图,该功能是通过 input[type=file] 来实现图片资源的获取,然后通过修改 CSS 将它设置成新的画布背景。document.getElementById('upload').onchange = function () { var imageFile = this.files[0]; var newImg = window.URL.createObjectURL(imageFile); clear(); document.getElementById('sketch').style.cssText += ` background: url($); background-size: cover; background-position: center; `; document.getElementById('maskInner').style.cssText += ` background: url($); background-size: cover; background-position: center; `; };到这里,全部功能都实现完毕了,大家赶快制作一张自己喜欢的 史诗皮肤 或 奥德赛小游戏 的启动页面吧。总结mask-image 遮罩元素feTurbulence 和 feDisplacementMap svg滤镜filter 属性Canvas 绘制方法TimelineMax 动画input[type=file] 本地图片资源加载
2022年11月08日
156 阅读
0 评论
0 点赞
2022-09-26
js实现扫雷小游戏
思路流程写出基本的布局利用js生成扫雷的table表格利用随机数来做地雷在表格中的索引初始化table表格根据地雷的坐标生成地雷周围的数字点击事件分成鼠标左键点击和右键点击左键点击情况下又分为点到的是地雷和非地雷两种情况点到的是地雷情况下,则将全部地雷显示,其他样式不变,并且不能再进行任意表格内的点击事件(左键右键都不行)点到的是非地雷情况下又分为点击的数字是0和非0两种情况如果是非0,则只需要显示其数字如果是0,利用递归思想,遍历周围的表格,若为0则继续递归显示0,直到遇到非0停止接上面的6,若进行右键点击,则显示小红旗,并且剩余地雷数-1当剩余雷数为0时,判断小红旗底下是否全为地雷,若全是地雷则成功扫雷,否则扫雷失败为按钮添加功能,分别为9乘以9->10个雷、16乘以16->40个地雷、28乘以28、99个地雷,以及重新开始按钮生成游戏棋盘利用双层for循环创建设定的棋盘大小为每个单元格的dom元素创建一个属性,该属性用于保存单元格的所有信息,如x,y坐标,value,是否为雷等随机生成炸弹利用随机数,随机生成炸弹x,y坐标,并将符合该坐标信息的单元格的属性更改为雷炸弹是在用户第一次点击的时候生成,防止用户第一次点击到炸弹将生成的每个炸弹信息都保存到一个this变量中,方便后续使用遍历每个炸弹周围的非炸弹方格,每遍历一次value值+1鼠标左键点击点击的时候需要考虑该单元格是否有被标记小旗子(isFlag属性),如果有则无法点击判断是雷还是数字,雷的话则游戏结束,数字则继续判断是否等于0,等于0则使用递归显示空白区域每次打开一个单元格,需要更改该单元格的isOpen属性,表示单元格被打开鼠标右键点击点击时需要考虑该单元格的isOpen属性是否被打开,打开的话则无法点击当该单元格没有标记旗帜时标记,如果有标记旗帜则取消标记每标记一个方格,剩余炸弹数量-1,取消标记则+1游戏结束当左键点击到炸弹的时候游戏结束。失败剩余炸弹数量为0时。判断旗帜标记是否正确,正确游戏胜利,标记有误则失败html代码<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <link rel="stylesheet" href="css/index.css" > </head> <body> <div class="main"> <header class="header"> <button>初级</button> <button>中级</button> <button>高级</button> </header> <div class="gameBox" id="gameBox"></div> <footer class="footer">剩余雷数量:<span id="surplusMine"></span> </footer> </div> </body> <script src="js/index.js"></script> </html>css代码.main .header { text-align: center; margin: 20px auto; } .main .gameBox table { border-spacing: 1px; background-color: rgb(170, 170, 170); text-align: center; margin: 20px auto; } .main .gameBox table td.mine { /* 游戏结束时显示 */ border: none; background: url(./../img/mine.png) no-repeat; background-size: 90% 90%; background-color: #e9e6e6; background-position: 2px 0; } .main .gameBox table td.targetMine { /* 游戏结束时显示,触发雷的单元格 */ border: none; background: url(./../img/mine.png) no-repeat; background-size: 90% 90%; background-color: #ff4b4b; background-position: 2px 0; } .main .gameBox table td.targetFlag { /* 右键标记方格时显示 */ background: url(./../img/flag.png) no-repeat; background-size: 90% 90%; background-position: 2px 0; background-color: #e9e6e6; } .main .gameBox table td { /* 单元格初始样式 */ width: 20px; height: 20px; box-sizing: border-box; border: 2px solid; border-color: #eee #ccc #ccc #eee; background-color: #e9e6e6; font-size: 1px; font-weight: 800; } .gameBox table td.zero, .gameBox table td.one, .gameBox table td.two, .gameBox table td.three, .gameBox table td.four, .gameBox table td.five, .gameBox table td.six, .gameBox table td.seven, .gameBox table td.eight, .gameBox table td.nine { border: none; background-color: rgb(211, 200, 200); } .gameBox table td.zero .gameBox table td.one { color: blue; } .gameBox table td.two { color: rgb(5, 93, 5); } .gameBox table td.three { color: #008c8c; } .gameBox table td.four { color: crimson; } .gameBox table td.five { color: rgb(228, 91, 0); } .gameBox table td.six { color: darkorange; } .gameBox table td.seven { color: rgb(193, 196, 50); } .gameBox table td.eight { color: pink; } .main .footer { text-align: center; }js代码function Game(tr, td, mineNum) { this.td = td; this.tr = tr; this.mineNum = mineNum; //存储预设或设定的炸弹总数,用于后续判断是否胜利使用 this.surplusMine = 0; //剩余雷数 this.mineInfo = []; //用于接收随机生成的雷的信息 this.tdsArr = [] //存放单元格的信息 this.isPlay = false; //是否开始玩 this.openClass = ["zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"] this.gameBox = document.getElementById("gameBox"); this.table = document.createElement("table"); //生成table标签 this.footerNum = document.getElementById("surplusMine"); //剩余炸弹数量显示框 } Game.prototype.creatDom = function() { //创建游戏区域,在玩家第一次点击游戏区域的时候执行 this.table.oncontextmenu = function() ; //清除默认右键单机事件 for (var i = 0; i < this.gameBox.children.length; i++) { //为防止重新开始游戏时,重复生成多个table,在添加之前先删除之前的 var childNod = this.gameBox.children[i]; this.gameBox.removeChild(childNod); } for (var i = 0; i < this.tr; i++) { var tr = document.createElement("tr"); this.tdsArr[i] = []; //为每一行生成一个数组 for (var j = 0; j < this.td; j++) { var td = document.createElement("td"); tr.appendChild(td); //将生成的td插入到tr中 this.tdsArr[i][j] = td; td.info = { //info属性包括了单元格的所有信息,很重要 type: "number", //格子类型,用于判断是否时炸弹 x: i, //行 y: j, //列 value: 0, //当该格子周围有炸弹时显示该数值,生成炸弹的时候会++ isOpen: false, //判断该单元格是否被打开 isFlag: false //判断是否有标记flag } } this.table.appendChild(tr); //见tr插入到table中 } this.gameBox.appendChild(this.table); } Game.prototype.creatMine = function(event, target) { //生成炸弹,该方法会在用户第一次点击棋盘的时候执行一次 var This = this; for (var i = 0; true; i++) { //随机生成炸弹,生成扎当数与设定扎当书mineNum相同时终止循环 var randomX = Math.floor(Math.random() * this.tr), //随机生成炸弹的行数 randomY = Math.floor(Math.random() * this.td); //随机生成炸弹的列数 // console.log(randomX + " " + randomY) if (target.info.x != randomX || target.info.y != randomY) { //保证第一次点击的时候不是炸弹 if (this.tdsArr[randomX][randomY].info.type != "mine") { //保证每次生成的雷的位置不重复 this.tdsArr[randomX][randomY].info.type = "mine"; //单元格更改属性为雷 this.surplusMine++; //生成雷的数量+1 this.mineInfo.push(this.tdsArr[randomX][randomY]); //将生成的雷的信息存放到this变量中,方便后续使用 } if (this.surplusMine >= this.mineNum) { //当生成的炸弹数量等于设定的数量后跳出循环 break; } } } //为每个炸弹周围的方格添加数字 for (var i = 0; i < this.mineInfo.length; i++) { var around = this.getAround(this.mineInfo[i], This); //获取每个炸弹的周围方格 // console.log(this.getAround(this.mineInfo[i], This)) for (var j = 0; j < around.length; j++) { //将周围每个方格的value++ around[j].info.value += 1; } } } Game.prototype.getAround = function(thisCell, This) { //获取某个方格的周围非炸弹方格,需要传递一个单元格dom元素,Game的this var x = thisCell.info.x, //行 y = thisCell.info.y, //列 result = []; // x-1,y-1 x-1,y x-1,y+1 // x,y-1 x,y x,y+1 // x+1,y-1 x+1y x+1,y+1 for (var j = x - 1; j <= x + 1; j++) { for (var k = y - 1; k <= y + 1; k++) { if ( //游戏区域的边界,行数x和列数y不能为负数,且不能超过设定的行数和列数 j < 0 || k < 0 || j > (This.tr - 1) || k > (This.td - 1) || //同时跳过自身和周边是雷的方格 This.tdsArr[j][k].info.type == "mine" || (j == x && k == y) ) { continue; //满足上述条件是则跳过当此循环; } else { result.push(This.tdsArr[j][k]) //将符合的单元格push到result中返回 } } } return result; } Game.prototype.lifeMouse = function(event, target) { //左键点击事件 var This = this; //用变量的方式将Game的this传递到函数中 var noOpen = 0; //没有被打开的格子数量 if (!target.info.isFlag) { //表示该必须没有被右键标记才能鼠标左击 if (target.info.type == "number") { //是数字时,则可视化 function getAllZero(target, This) { //递归函数 // console.log(target.info) if (target.info.isFlag) { //当这个单元格之前有被标记过flag时,则将剩余炸弹数+1 This.surplusMine += 1; target.info.isFlag = false; //单元格被打开后初始化flag } if (target.info.value == 0) { //等于格子的value等于0的时候 target.className = This.openClass[target.info.value]; //可视化 target.info.isOpen = true; //表示该单元格被打开 var thisAround = This.getAround(target, This); //获取该单元格周围的格子信息 for (var i = 0; i < thisAround.length; i++) { // console.log(thisAround[i].info.isOpen) if (!thisAround[i].info.isOpen) { //递归的条件,当格子的open为true时不执行 getAllZero(thisAround[i], This) //执行递归 } } } else { target.innerHTML = target.info.value; target.className = This.openClass[target.info.value]; //可视化 target.info.isOpen = true; //表示单元格被打开 target.info.isFlag = false; //单元格被打开后初始化flag } } getAllZero(target, This); //首次执行 //每次鼠标左键点击的时候都需要检查一下没有被打开的方格数量,每有一个则noOpen++ for (var i = 0; i < this.tr; i++) { for (var j = 0; j < this.tr; j++) { if (this.tdsArr[i][j].info.isOpen == false) { noOpen++; } } } //当noOpen的数量与炸弹数量相同时,说明剩余的方格全是雷,游戏通过 if (noOpen == this.mineNum) { console.log(noOpen) this.gameWin(); } } else { //点击到了炸弹,游戏结束 this.gameOver(target) } } } Game.prototype.rightMouse = function(target) { //鼠标右键点击执行 if (!target.info.isOpen) { if (!target.info.isFlag) { //标记 target.className = "targetFlag"; //显示旗帜 target.info.isFlag = true; //表示该方格已经被标记 this.surplusMine -= 1; //每标记一个方格,剩余炸弹数量-=1 // console.log(this.surplusMine) } else { //取消标记 target.className = ""; //去掉旗帜 target.info.isFlag = false; this.surplusMine += 1; // console.log(this.surplusMine) } var isWin = true; if (this.surplusMine == 0) { //标记完所有flag时,遍历所有单元格 // console.log(this.mineInfo.length) for (var i = 0; i < this.mineInfo.length; i++) { console.log(this.mineInfo[i].info.isFlag) if (!this.mineInfo[i].info.isFlag) { //检查每个雷的isFlag属性是否被标记,只要有一个为false则输掉游戏 isWin = false; this.gameOver(target, 1); break; } } isWin ? this.gameWin(1) : 0; //三目运算符号 } // if (this.surplusMine == 0) { //标记完所有flag时,遍历所有单元格 // for (var i; i < this.tr; i++) { // for (var j; j < this.td; j++) { // if() // } // } // } } } Game.prototype.gameOver = function(target, code) { //游戏结束,code为触发代码,当旗用完了时为1,点击到炸弹为0 // console.log(this.mineInfo) var mineInfoLen = this.mineInfo.length; for (var i = 0; i < mineInfoLen; i++) { //显示每个雷的位置 this.mineInfo[i].className = "mine"; } this.table.onmousedown = false; //取消鼠标事件 if (code) { alert("旗帜用完了,没有排除所有雷,游戏结束") } else { target.className = "targetMine"; //触发雷标红色 alert("你被炸弹炸死了,游戏结束") } } Game.prototype.gameWin = function(code) { //游戏胜利 if (code) { alert("你成功标记所有地雷,游戏通过") } else { alert("你找到了所有安全区域,游戏通过") } this.table.onmousedown = false; } Game.prototype.play = function() { var This = this; //需要将this传递到事件函数中使用 this.table.onmousedown = function(event) { event = event || window.event; //兼容IE target = event.target || event.srcElement //兼容IE if (!this.isPlay) { //首次点击初始化棋盘,随机生成炸弹 this.isPlay = true; This.creatMine(event, target); } if (event.button == 0) { //鼠标左键点击时执行 This.lifeMouse(event, target); } else if (event.button == 2) { //右键点击执行 This.rightMouse(target) } This.footerNum.innerHTML = This.surplusMine; //每次点击右键,刷新页面下方的剩余雷数 } } Game.prototype.tablePos = function() { //将table居中显示 var width = this.table.offsetWidth, height = this.table.offsetHeight; // console.log(this.table.offsetWidth) this.table.style.width = width + "px "; this.table.style.height = height + "px " } function addEvent(elem, type, handle) { //添加事件函数 if (elem.addEventListener) { //w3c标准 elem.addEventListener(type, handle, false); } else if (elem.attachEvent) { //IE9及以下 elem.attachEvent("on" + type, function() { handle.call(elem); }) } else { //其他情况 elem["on" + type] = handle; } } Game.prototype.setDegree = function() { //调整难度 var button = document.getElementsByTagName("button"); addEvent(button[0], "click", function() { //简单 var game = new Game(10, 10, 10); game.creatDom(); game.play(); game.tablePos(); }); addEvent(button[1], "click", function() { //一般 var game = new Game(16, 16, 50); game.creatDom(); game.play(); game.tablePos(); }); addEvent(button[2], "click", function() { //困难 var game = new Game(30, 30, 125); game.creatDom(); game.play(); game.tablePos(); }); } // 默认棋盘 var game = new Game(10, 10, 10); game.creatDom(); game.play(); game.tablePos(); game.setDegree()扫雷源码
2022年09月26日
61 阅读
3 评论
0 点赞
2022-03-16
js防抖和节流
js防抖什么是函数防抖概念:函数防抖(debounce),就是指触发事件后,在 n 秒内函数只能执行一次,如果触发事件后在 n 秒内又触发了事件,则会重新计算函数延执行时间。应用场景(1) 用户在输入框中连续输入一串字符后,只会在输入完后去执行最后一次的查询ajax请求,这样可以有效减少请求次数,节约请求资源;(2) window的resize、scroll事件,不断地调整浏览器的窗口大小、或者滚动时会触发对应事件,防抖让其只触发一次;function debounce(fn, delay) { let timer return function(arges) { clearInterval(timer); timer = setTimeout(() => { fn(); }, delay) } }js节流什么是节流规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。应用场景(1)鼠标连续不断地触发某事件(如点击),只在单位时间内只触发一次;(2)在页面的无限加载场景下,需要用户在滚动页面时,每隔一段时间发一次 ajax 请求,而不是在用户停下滚动页面操作时才去请求数据;(3)监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断;function throttle(fn, delay) { let timer; return function() { if (!timer) { timer = setTimeout(() => { timer = null fn() }, delay); } } }和防抖区别是,防抖是间隔时间后最后执行一次,节流是按间隔时间连续执行.时间戳截流——首节流用最新触发的时间减去上一次回调执行的时间,如果大于等于 wait 则会执行回调时间戳节流在第一次触发时会立刻执行。在第一次操作的时候就会执行一遍fn,之后的操作如果时间不超过delay(例如2s)就不会在执行函数,理解为第一次执行,最后一次不执行function throttle(func, wait) { let previous = 0 return function throttled(...args) { const ctx = this const now = Date.now() const remain = wait - (now - previous) if (remain <= 0) { func.apply(ctx, args) previous = now } } } 定时器节流——尾节流指定时间之后执行回调,触发一个定时器之后,即使再次触发节流函数,也不会导致定时器推迟执行(事件循环或者同步阻塞会导致延迟执行,在这里不用考虑)。它不会清除已经开始的定时器,而是等待定时器被执行之后才再开始下一个定时器。理解为第一次不执行,最后一次执行function throttle(func, wait) { let timeout = 0 return function throttled(...args) { const ctx = this // 如果已经是定时器定时阶段,则直接跳过,相当于忽略了触发 // 必须等到定时器到时间之后 if (!timeout) { timeout = setTimeout(() => { func.apply(ctx, args) timeout = null }, wait) } } } 兼顾型节流,就能够在第一次,最后一次都执行代码。function throttle(fn, delay){ let last=0; let timer=null; return function(){ let now=Date.now(); let reming=delay-(now-last); clearTimeout(timer);//不管三七二十一,有定时器都先清除定时器 if(reming<0){ fn.apply(this, arguments); last=now; }else{ timer=setTimeout(()=>{ fn.apply(this,arguments); }, reming); } } }
2022年03月16日
48 阅读
7 评论
0 点赞