整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

任天堂体感运动新作免费试玩!《雪人兄弟》儿时经典将登陆NS

DS和WiiU商店明年关闭

任天堂官推宣布,将于2023年3月末关闭3DS和WiiU的eShop,关闭后玩家将无法购买任何内容,但此前已经购买的内容仍可重新下载,游戏更新、线上服务等功能还可以继续使用。

此外,任天堂还推出了一个3DS和WiiU的统计页面,登录后可以查看对应机种的总游戏时间和玩过的游戏数量等数据。

传送门:https://my-nintendo-3ds-wiiu-memories.nintendo.com/ja-JP/index.html#top

宝可梦银行明年免费

任天堂宣布,在2023年3月末3DS商店关闭后,宝可梦银行和宝可梦传送将无法从商店下载,同时宝可梦银行将改为免费使用,可以将宝可梦银行中的宝可梦搬家至“宝可梦Home”,该服务的终止时间未定,已经充值过的不予退款。

PS

今天发布的另一篇文章中有历代宝可梦的传送方法,有需要的小伙伴可以看看[狗头]

《茶杯头》动画

网飞公开《茶杯头》动画剧集《The Cuphead Show!》新预告片,本片将于2月18日在网飞平台开播。


本周Fami通游戏评分

《宝可梦 阿尔宙斯》

(Switch) – 10/9/9/10 [38/40]

《苏菲的炼金工房2 ~不可思议梦的炼金术士~》

(PS4, Switch) – 8/8/8/8 [32/40]



《NS运动》免费试玩

任天堂开放《Nintendo Switch 运动》网络测试资格申请,此次测试仅限Nintendo Switch Online会员参与,在下方各服务器的传送门可前往免费兑换参与资格,之后在对应eShop兑换下载即可,客户端容量855M。

美服:https://my.nintendo.com/rewards/7465c9bc0183c858

日服:https://my.nintendo.com/rewards/ab1705a64f670376

欧服:https://my.nintendo.com/rewards/5ef22cb53f7e0914

港服:无

本次测试游玩对应3种项目:保龄球、网球、击剑,具体开放时间如下

2月19日:11:00~11:45、19:00~19:45

2月20日:3:00-3:45、11:00-11:45、19:00-19:45

想试玩的小伙伴可以按照以下步骤操作:

1.确认账号是日服/美服/欧服:

确认方法:打开eShop,里面的语言是日语、英语或者其他小语种就行,只要不是繁体中文即可。

改区方法:打开并登陆https://my.nintendo.com,里面有国家/地区的选项。港服有余额的话,转区余额会被清零。登陆的时候可能需要代理,有的加速器也支持,下同。

2.获取兑换码:

手机/电脑打开My Nintendo官网并登陆账号:https://my.nintendo.com。

找到《Nintendo Switch Sports》的页面,多次点击最下面的蓝色按钮,最后的页面里选择兑换即可获取测试客户端的兑换码。

3.使用兑换码:

在NS的eShop里使用该兑换码即可。兑换码是分区的,你在哪个区获得的,就得在哪个区使用。

经典名作《雪人兄弟》

经典街机游戏《雪人兄弟 特别版》确认将登陆NS,发售日和售价未定。

本作继承了街机原版的可爱设计和操作性并进行了一定程度的升级强化,此外,在原版50关的基础上再增加30关,追加新BOSS和新怪物。

另外,本作还将包含“怪物挑战模式”,该模式中玩家可以选择怪物当作主角进行闯关,每个怪物都有不同的技能,可以体验与正常闯关完全不同的趣味玩法。该模式将收录在实体版游戏中,数字版需要额外购买。


《异度神剑3》角色介绍

官推公开了本作部分角色介绍,本作预计将于9月登陆Switch,支持中文。(注:以下内容来自日文官推,部分用语为暂译)

Yuuni(CV:潘めぐみ)

Yuuni是18岁的科维斯士兵,使用杖型的枪械“枪杖”。她与Noa和Rantsu是青梅竹马,擅长在战斗中进行回复,长在头上的翅膀好像在哪见过!?

Yuuni拥有心直口快的性格,因此有点毒舌,虽然是队伍里最大大咧咧的人,但却很擅长讲解授课。很尊敬Noa,与Rantsu保持着无话不谈的良好关系,对朋友的责任感比任何人都强。

PS

按照异度1的设定,头上有小翅膀的海恩塔族是非常长寿的,例如梅利亚已经88岁了,但相当于人类的16岁。那Yuuni这18岁的年纪,在海恩塔族里就相当于还是婴儿了[捂脸]

但有没有这样一种可能,18岁只是按照人类推算的大致年龄,实际是个100多岁的老阿姨[捂脸]

Taion(CV:木村良平)

Taion是18岁的安格努斯士兵,使用纸样的神秘武器“纸人”,还为其起名为“Mondo”并疼爱有加。他是一位深思熟虑的战术家,擅长支援同伴。黑色眼镜和橙色围巾是他的标志。

他与Mio和Sena共同行动,因拥有高水平的洞察力和战术判断能力深受同伴信赖。拥有一丝不苟性格,有时也会过于慎重,在不耐烦的时候也会变得粗暴起来,让人有点困扰。


《阿尔宙斯》新宣传片

宝可梦官方油管频道公开《宝可梦传说 阿尔宙斯》新宣传片,“新资讯 其伍 未曾谋面的宝可梦与场长篇”。本作已于日前登陆Switch,支持中文。


动森更新

《集合啦!动物森友会》更新2.0.5版本,具体内容如下。

本体

-修复在特定情况下玩家搬家导致摄影棚的说明无法进行的问题

-修复在岛上摆放了过多家具等物品并发生对话时,对话框出现的时候会导致游戏无法进行的问题

-修复在梦境搜索中选择“按岛屿名称搜索”时,梦美会重复相关说明的问题

-修复小动物穿着在“Able Sisters”中展示的“我的设计”时,会穿上其他样式服装的问题

-修复小动物访问玩家的家时,如果玩家使用了“我的设计展示柜”、“设计”、“房间草稿”中的任意一个后,玩家离开家后小动物仍会留在家里的问题

-修复在“对岛民的家进行提案”并挂上门饰后,日期变更会导致门饰消失的问题

-修复/调整其他问题,提升游戏体验

DLC

-修复在未满足蚂蚁、蝴蝶的出现条件的情况下,在别墅庭院里放置蚂蚁、蝴蝶会导致再来别墅时无限加载的问题

-修复在建造设施的特定时间结束游戏,会导致之后的事件无法正常进行的问题

-修复/调整其他问题,提升游戏体验


《智龙迷城 NS版》

一款3消解谜游戏,将于2月20日登陆eShop,售价500日元,有氪金要素,无中文,无实体版。

玩家将在游戏中利用怪物们和3消解谜的力量进行冒险,共有3种模式“冒险”“对战”“编辑”。冒险模式支持单人和最多4人合作,目标为打通所有迷宫;对战模式支持最多8人进行分数对决;编辑模式中可以自由搭配敌人、背景、BGM创造独特迷宫并上传分享。


《黎明杀机》x贞子姐姐

《黎明杀机》将于3月9日上线“贞子再临”联动内容,追加新杀手贞子、新逃生者浅川阳一。

件被看作是 JavaScript 与网页之间交互的桥梁,当事件发生时,可以通过 JavaScript 代码(函数)执行相关的操作。例如,用户可以通过鼠标拖曳登录框,改变登录框的显示位置;或者在阅读文章时,选中文本后自动弹出分享、复制选项。本章将对 DOM 中的事件进行详细讲解。

事件处理


事件可被理解为是 JavaScript 侦测到的行为,这些行为指的就是页面的加载、鼠标单击页面、鼠标滑过某个区域、按下键盘等具体的动作,它对实现网页的交互效果起着重要的作用。在深入学习事件时,需要对一些非常基本又相当重要的概念有一定的了解。


  • 事件处理程序


事件处理程序指的就是 JavaScript 为响用户行为所执行的程序代码。例如,用户单击 button 按钮时,这个行为就会被 JavaScript 中的click 事件侦测到;然后让其自动执行,为 click 事件编写的程序代码,如在控制台输出“按钮被单击了”。


  • 事件驱动


事件驱动是指,在 Web 页面中 JavaScript 的事件,侦测到的用户行为(如鼠标单击、鼠标移入等),并执行相应的事件处理程序的过程。

事件绑定


事件绑定指的是为某个元素对象的事件绑定事件处理程序。在 DOM 中提供了3种事件的绑定方式。下面将针对以3种事件绑定方式的语法以及各自的区别进行详细讲解。

行内绑定方式


事件的行内绑定式是通过HTML标签的属性设置实现的,具体语法格式如下。

<div onclick="alert('handle click')"></div>

在上述语法中,div 可以是任意的HTML标签,如 <button>、<input>标签等;事件是由 on 和事件名称组成的一个 HTML 属性,如单击事件对应的属性名为 onclick;事件的处理程序指的是 JavaScript 代码,如匿名函数等。

需要注意的是,由于开发中提倡 JavaScript 代码与 HTML 代码相分离。因此,不建议使用行内绑定事件。

动态绑定方式


动态的绑定方式很好地解决了JavaScript代码与HTML代码混合编写的问题。在JavaScript代码中,为需要事件处理的 DOM 元素对象,添加事件与事件处理程序。具体语法格式如下。

div.onclick = function handleClick () {
  console.log('handle click');
};

在上述语法中,事件的处理程序一般都是匿名函数或有名的函数。在实际开发中,相对于行内绑定来说,事件的动态绑定的使用居多。

行内绑定与动态绑定除了实现的语法不同以外,本质是相同的,都是给 DOM 元素绑定了一个 onclick 属性。

标准的绑定事件方式


为了给同一个 DOM 对象的同一个事件添加多个事件处理程序,DOM中引入了事件流的概念,可以让DOM对象通过事件监听的方式实现事件的绑定。由于不同浏览器采用的事件流实现方式不同,事件监听的实现存在兼容性问题。通常根据浏览器的内核可以划分为两大类,一类是早期版本的IE浏览器(如IE6~8),一类遵循W3C标准的浏览器(以下简称标准浏览器)。

接下来,将根据不同类型的浏览器,分别介绍事件监听的实现方式。


(1)早期版本的IE浏览器


在早期版本的IE浏览器中,事件监听的语法格式如下。

DOM对象.attachEvent(type,callback);

在上述语法中,参数 type 指的是为 DOM 对象绑定的事件类型,它是由 on 与事件名称组成的,如 onclick。。参数 callback 表示事件的处理程序。


(2)标准浏览器


标准浏览器包括IE8版本以上的IE浏览器(如IE9~11),新版的Firefox、Chrome等浏览器。具体语法格式如下。通过这种方式我们可以给元素注册多个事件处理函数,而 btn.onclick = fn 是赋值操作只能设置一个事件处理函数。

DOM对象.addEventListener(type, callback, [capture]);

在上述语法中,参数 type 指的是 DOM 对象绑定的事件类型,它是由事件名称设置的,如 click。参数 callback 表示事件的处理程序。参数 capture 默认值为 false,这个属性后面单独介绍,一般情况我们都使用它的默认值。

现在 IE 浏览器已经被淘汰,所以我们不需要再去记忆 attachEvent() 的用法,但是我们需要了解过去,过去在使用这种方式注册事件的时候需要处理浏览器的兼容性,下面我们演示下:

function addEventListener(element, type, listener) {
  // 能力检测: 就是要看当前的浏览器是否支持此标签对象的属性或是方法
  if (element.addEventListener) {
    element.addEventListener(type, listener, false);
  } else if (element.attachEvent) {
    element.attachEvent('on' + type, listener);
  } else {
    element['on' + type] = listener;
  }
}
  • 事件处理函数中的 this

在事件处理函数中的 this 指向当前触发该事件的 DOM 元素。

link.onclick = function handleLink () {
  photo.src = this.href;
  p.textContent = this.title;
  return false;
};

但是通过行内绑定注册的事件,调用的函数中 this 指向的是 window。

<button onclick="handle()">按钮</button>
<script>
  function handle () {
    // 此处的 this 指向 window
    console.log(this);
  }
</script>

行内绑定事件 onclick="handle()" 中的 "" 双引号内部其实可以看做是一个匿名函数,"" 双引号内部的这个匿名函数才是事件处理函数,在事件处理函数中又调用了 handle() 方法。

<!-- 此处的 this 指向的是触发事件的对象 button -->
<button onclick="handle(this)">按钮</button>
<script>
  function handle (btn) {
    // 此处的 this 指向 window
    console.log(btn);
  }
</script>

解除事件

绑定事件的元素可以解除绑定,例如:我们可以让按钮点击一次之后解除事件绑定。三种绑定事件的解除事件的方式不同,下面我们分别来介绍。

行内绑定事件和动态绑定事件本质上都是给 DOM 元素设置 onclick 属性,对应的解除绑定事件的方式都是把 onclick 属性重新设置为 null。

  • 解除行内绑定的事件

当按钮执行完点击事件的处理程序后立即解除事件的绑定

<button onclick="handle(this)">按钮</button>
<script>
  function handle (btn) {
    alert('Hello');
    btn.onclick = null;
  }
</script>
  • 解除动态绑定的事件
btn.onclick = function handle () {
	this.onclick = null;
};


  • 解除标准绑定事件的方式


标准绑定事件使用 addEventListener(type, callback, [capture]); 方法,对应的解除绑定使用的方法是 removeEventListener(type, callback, [capture]),需要注意的是,如果注册的事件需要解除的话,使用 addEventListener() 注册事件的时候,传入的 callback 不能是匿名函数,因为解除事件绑定的时候还需要引用这个函数。

const div = document.querySelector('#div');

div.addEventListener('click', handle);

function handle () {
  alert('hello');
  this.removeEventListener('click', handle);
}

事件流


我们已经会使用 addEventListener(type, callback, [capture]),方法给元素注册事件,但是这个方法的第三个参数的作用我们还不清楚,下面我们就来介绍该方法的第三个参数,这里我们需要先来学习 DOM 中的事件流(事件模型)。


DOM (文档对象模型)结构是一个树型结构,当一个HTML元素产生一个事件时,该事件会在元素结点与根节点之间按特定的顺序传播,路径所经过的节点都会收到该事件,这个传播过程可称为 DOM 事件流。


事件顺序有两种类型:事件捕捉和事件冒泡。


事件冒泡(Event Bubbling)


这是 IE 浏览器对事件模型的实现,也是最容易理解的。冒泡,顾名思义,事件像个水中的气泡一样一直往上冒,直到顶端。

从DOM 树型结构上理解,就是事件由叶子节点沿祖先结点一直向上传递直到根节点;从浏览器界面视图 HTML 元素排列层次上理解就是事件由具有从属关系的触发事件的元素一直传递到根元素直到文档对象。



addEventListener(type, callback, [capture]),该方法的第三个参数为 false 的时候设置触发事件的方式为事件冒泡,该参数默认为 false

一般情况下,我们都会使用事件冒泡的方式注册事件。


const outer = document.querySelector('#outer');
const inner = document.querySelector('#inner');

outer.addEventListener('click', function () {
  console.log('点击了 outer');
}, false);

inner.addEventListener('click', function () {
  console.log('点击了 inner');
}, false);

document.body.addEventListener('click', function () {
  console.log('点击了 body');
}, false);

document.addEventListener('click', function () {
  console.log('点击了 document');
}, false);

window.addEventListener('click', function () {
  console.log('点击了 window');
}, false);

执行结果:

使用行内绑定和动态绑定事件的方式默认使用的是事件冒泡。


事件捕获(Event Capturing)


Netscape 的实现,它与冒泡型刚好相反,由 DOM 树最顶层元素一直到触发事件的元素。



addEventListener(type, callback, [capture]),该方法的第三个参数为 true 的时候设置触发事件的方式为事件捕获。


const outer = document.querySelector('#outer');
const inner = document.querySelector('#inner');

outer.addEventListener('click', function () {
  console.log('点击了 outer');
}, true);

inner.addEventListener('click', function () {
  console.log('点击了 inner');
}, true);

document.body.addEventListener('click', function () {
  console.log('点击了 body');
}, true);

document.addEventListener('click', function () {
  console.log('点击了 document');
}, true);

window.addEventListener('click', function () {
  console.log('点击了 window');
}, true);

执行结果:


使用行内绑定和动态绑定事件的方式无法使用事件捕获。


DOM标准的事件模型


我们已经对上面两个不同的事件模型进行了解释和对比。DOM 标准同时支持两种事件模型,即事件捕获与事件冒泡,但是,事件捕获先发生。两种事件流都会触发 DOM 中的所有对象,从 document对象开始,也在 document 对象结束(大部分兼容标准的浏览器会继续将事件是捕捉/冒泡延续到window 对象)。


如图:首先是捕获传递事件,接着是冒泡传递,所以,如果一个处理函数既注册了捕获型事件的监听,又注册冒泡型事件监听,那么在 DOM 事件模型中它就会被调用两次。

DOM 标准的事件模型最独特的性质是,文本节点也会触发事件(在IE不会)。


事件委托


事件委托,通俗地来讲,就是把一个元素的处理事件的函数委托到另一个元素。

一般来讲,会把一个或者一组元素的事件委托到它的父层或者更外层元素上,真正绑定事件的是外层元素,当事件响应到需要绑定的元素上时,会通过事件冒泡机制从而触发它的外层元素的绑定事件上,然后在外层元素上去执行函数。


举个例子,比如一个宿舍的同学同时快递到了,一种方法就是他们都傻傻地一个个去领取,还有一种方法就是把这件事情委托给宿舍长,让一个人出去拿好所有快递,然后再根据收件人一一分发给每个宿舍同学。


在这里,取快递就是一个事件,每个同学指的是需要响应事件的 DOM 元素,而出去统一领取快递的宿舍长就是代理的元素,所以真正绑定事件的是这个元素,按照收件人分发快递的过程就是在事件执行中,需要判断当前响应的事件应该匹配到被代理元素中的哪一个或者哪几个。


下面我们来做一个练习,为下面的每一个 li 注册点击事件,当点击当前 li 的时候打印 li 中的文本内容。

<ul id="list">
  <li>item 1</li>
  <li>item 2</li>
  <li>item 3</li>
  ......
  <li>item n</li>
</ul>

首先我们用传统的方式来实现,先获取到页面上所有的 li,然后遍历所有的 li,给每一个 li 注册点击事件,这里使用 addEventListener() 注册事件的时候省略了第三个参数,默认为 false,事件冒泡的方式。

这样做不好的地方有两点,第一:我们需要为每一个 li 元素创建一个新的事件处理函数,每次创建都需要销毁时间和内存。第二:当点击按钮往 ul 中添加新的 li 元素的时候需要给新创建的 li 注册点击事件。

const lis = document.querySelectorAll('#list li');
    
lis.forEach(function (li) {
  li.addEventListener('click', function () {
    console.log(this.textContent)
  });
});

下面我们使用事件委托的方式优化上面的代码,把点击事件注册给父元素 ul,当点击 li 的时候通过事件冒泡把点击事件传递给父元素 ul。

const ul = document.querySelector('#list');
ul.addEventListener('click', function () {
  console.log('test');
  // 此处的 this 是注册事件的元素 ul
  console.log(this);
});

代码改完之后点击 li,这段代码确实可以执行,但是我们的目标是打印 li 之间的内容,而通过打印发现此处的 this 不是我们想要的当前点击的 li,而是注册事件的元素 ul。所以这里需要强调一点,在注册事件的时候,事件源是注册事件的对象。


那如何获取当前触发事件的元素 li 呢?当事件被触发的时候事件处理函数会接收一个参数,这个参数叫做事件对象,事件对象可以提供触发事件的时候相关的数据,下一小节详细介绍,这里我们先用事件对象解决当前的问题,事件对象中有一个 target 属性,这个属性就是当前触发事件的对象。

在 IE 浏览器中获取事件对象的方式不同,IE 中是通过 window.event 获取事件对象,以前在获取事件对象的时候还要处理浏览器兼容性问题,IE 浏览器现在已经被淘汰所以浏览器兼容性的处理我们就不再演示。


const ul = document.querySelector('#list');
// 事件参数(对象) e
ul.addEventListener('click', function (e) {
  // e.target 触发事件的元素
  console.log(e.target.textContent);
  // 注册事件的元素
  console.log(this);
});

到这里这个案例就完成了,我们再来扩展下这个案例,如果想要点击特定的 li 来触发事件该如何实现?

<ul id="list">
  <li>item 1</li>
  <li class="cls">item 2</li>
  <li class="cls">item 3</li>
  ......
  <li>item n</li>
</ul>

如上代码,如果想点击具有特性类样式或者特定 id 的元素触发事件,可以通过判断当前点击的元素 e.target 的类样式或者 id 属性进行判断。

if (e.target.className === 'cls') {
	// ....
}

但是如果想像 CSS 选择器一样更加灵活的匹配的话,上面的判断不够灵活,这里可以使用 元素.matches(选择器) 来匹配特定元素。当元素匹配指定的选择器返回 true。

const ul = document.querySelector('#list');
ul.addEventListener('click', function (e) {
  // matches 方法,当元素匹配指定的选择器返回 true
  if (e.target.matches('.cls')) {
    console.log(e.target.textContent);
  }
});


利用事件冒泡的特性,将本应该注册在子元素上的处理事件注册在父元素上,这样点击子元素时发现其本身没有相应事件就到父元素上寻找作出相应。这样做的优势有:1. 减少内存消耗,避免重复创建相同事件处理函数,只需要把多个子元素的事件委托给父元素。2.随时可以添加子元素,添加的子元素会自动有相应的处理事件。

案例:购物车删除


  • 需求:
    • 使用事件委托优化移除购物车数据的功能
  • 代码:
const tbody = document.querySelector('tbody');
tbody.addEventListener('click', function (e) {
  // 注册事件的元素 tbody
  // console.log(this);
  // 触发事件的元素(你点击的那个元素)
  // console.log(e.target)
  // 判断元素是否是指定的元素
  // console.log(e.target.matches('.del'))

  if (e.target.matches('.del')) {
    e.target.parentNode.parentNode.remove();
  }
});


事件对象


每当触发一个事件,就会产生一个事件对象 event,该对象包含着所有与事件有关的信息。包括导致事件的元素、事件的类型以及其他与特定事件相关的信息。上一小节中我们使用事件对象获取触发事件的元素。


例如:鼠标操作产生的 event中会包含鼠标位置的信息;键盘操作产生的event中会包含与按下的键有关的信息。


所有浏览器都支持 event 对象,但支持方式不同,在标准 DOM 中 event 对象必须作为唯一的参数传给事件处理函数,在 IE 中 event 是 window 对象的一个属性。


  • 事件对象提供的常用成员


成员

描述

备注

type

触发的事件名称


eventPhase

事件流在传播阶段的位置


target

触发事件的元素


srcElement

target 的别名,老版本的 IE 中使用


clientX / clientY

基于浏览器的可视区域,鼠标坐标值

可配合固定定位,基于窗口定位

pageX / pageY

基于整个页面,页面滚动有关,鼠标在页面的坐标值

可配合绝对定位,基于页面定位

key

获取按键输入


preventDefault()

取消默认行为


stopPropagation()

阻止事件冒泡



案例:跟着鼠标飞的图片


  • 需求:
    • 当鼠标移动的时候让图片跟着鼠标走
  • 代码:
    • 因为要改变图片位置,所以让图片脱离文档流
    • 注册 mousemove 事件
    • 改变图片的坐标:鼠标坐标 - 图片大小的一半,让鼠标在图片的中央位置
const img = document.querySelector('#img');
document.addEventListener('mousemove', function (e) {
  // 鼠标位置 - 图片大小的一半
  img.style.left = e.clientX - 96 / 2 + 'px';
  img.style.top = e.clientY - 80 / 2 +  'px';
});

设置样式,让 body 的高度等于 1500px(垂直方向出现滚动条),滚动条下拉这时候移动鼠标,图片的纵向位置跟鼠标脱离。

原因是 clientX 和 clientY 获取的是鼠标在当前可视区域的位置。如果出现滚动条的话可以通过 pageX 和 pageY 获取鼠标在当前文档中的位置。

const img = document.querySelector('#img');
document.addEventListener('mousemove', function (e) {
  img.style.left = e.pageX - 96 / 2 + 'px';
  img.style.top = e.pageY - 80 / 2 +  'px';
});

这里获取图片大小的时候写的是具体值,将来图片替换后,还需要改变这里的大小。我们可以使用 getComputedStyle() 获取图片的大小。


const img = document.querySelector('#img');
img.addEventListener('load', function () {
  const style = window.getComputedStyle(img, null);
  const imgWidth = parseInt(style.width);
  const imgHeight = parseInt(style.height);
  document.addEventListener('mousemove', function (e) {
    img.style.left = e.pageX - imgWidth / 2 + 'px';
    img.style.top = e.pageY - imgHeight / 2 +  'px';
  });
});

注意:这里需要在 img 标签加载完毕后获取图片的大小,否则获取到的图片大小是 0,因为 load 事件代表图片被加载,否则的话代码从上到下执行到这个位置,图片还没有被下载回来,这个时候获取图片的大小是 0。

案例:键盘控制图片移动


  • 需求:
    • 按方向键盘控制页面上的图片往相应的方向移动。
  • 代码:
    • 让图片脱离文档流,翻转 180 的类样式,可以左右移动
    • 监听键盘按下的事件
    • 根据按下的方向键,控制图片的坐标改变 10 像素
#img {
  width: 100px;
  position: absolute;
  left: 0;
  top: 0;
}
.toLeft {
  transform: rotateY(180deg);
}
const img = document.querySelector('#img');
let x = 0;
let y = 0;
document.addEventListener('keydown', function (e) {
  switch (e.key) {
    case 'ArrowLeft':
      x -= 10;
      img.classList.add('toLeft');
      break;
    case 'ArrowRight':
      x += 10;
      img.classList.remove('toLeft');
      break;
    case 'ArrowUp':
      y -= 10;
      break;
    case 'ArrowDown':
      y += 10;
      break;
  }

  img.style.left = x + 'px';
  img.style.top = y + 'px';
});

案例:禁止弹出右键和选中文字

// contextmenu 鼠标右键事件
document.addEventListener('contextmenu', function(e) {
  // 禁止点击的默认行为,即显示上下文菜单
  e.preventDefault()
});
// 禁止选中文字事件
document.addEventListener('selectstart', function(e) {
  // 禁止选中文字的默认行为,即不能选中文字
  e.preventDefault()
})

案例:拖拽登录框

  • 需求:
    • 点击弹出登录框,显示登录框和遮罩层
    • 鼠标放到登录框的头部,显示可移动的鼠标样式
    • 单按下鼠标可以拖动登录看的位置,鼠标弹起移除拖动的功能
  • 代码:
    • 当鼠标按下弹出登陆框的a标签的时候,弹出两个层来
    • 单击关闭按钮的时候,隐藏这两个层
    • 当鼠标按下title这个盒子的时候,就能够获取鼠标在盒子中的坐标位置
    • 鼠标在文档中移动的时候,时时的获取坐标,减去在盒子中的坐标,将这个值赋值给login的left和top
    • 鼠标离开的时候,在清空事件处理程序,不要再去触发移动的事件中的事件处理程序了
const loginBg = document.querySelector('#bg');
const loginLink = document.querySelector('#link');
const loginBox = document.querySelector('#login');
const closeBtn = document.querySelector('#closeBtn');
const loginTitle = document.querySelector('#title');

loginLink.addEventListener('click', function () {
  loginBox.style.display = 'block';
  loginBg.style.display = 'block';
});
closeBtn.addEventListener('click', function () {
  loginBox.style.display = 'none';
  loginBg.style.display = 'none';
});

// 拖动事件的三个过程:鼠标按下 mousedowm,鼠标移动 mousemove,鼠标松开 mouseup
const style = window.getComputedStyle(loginBox, null);
// 模态框跟着鼠标走的原理
loginTitle.addEventListener('mousedown', function (e) {
  const loginLeft = parseInt(style.left);
  const loginTop = parseInt(style.top);
  // 步骤一:当鼠标按下时,需要立即得到鼠标在盒子中的坐标
  var x = e.pageX - loginLeft;
  var y = e.pageY - loginTop;
  // 为整个页面添加鼠标移动事件
  document.addEventListener('mousemove', move);

  function move(e) {
    // 步骤二:模态框的left和top等于鼠标在页面的坐标减去鼠标在盒子内的坐标
    // 注意:一定要加上px
    login.style.left = e.pageX - x + 'px';
    login.style.top = e.pageY - y + 'px';
  }
  // 步骤三:鼠标松开时取消整个页面的鼠标移动事件
  document.addEventListener('mouseup', function (e) {
    document.removeEventListener('mousemove', move);
  });
});

常用事件

在这之前我们已经使用过了单击事件、鼠标经过和鼠标离开的事件,浏览器给我们提供的事件种类非常多,下面我们列出一些常用的事件,使用的方式都是一样的。


  • 常用的事件


描述

事件名称

鼠标单击

click

鼠标双击

dblclick

鼠标移入

mouseover

鼠标移出

mouseout

鼠标移动

mousemove

获取焦点

focus

失去焦点

blur

键盘按下

keydown

键盘弹起

keyup

不能识别功能键 ctrl、alt 等

keypress

文本框的输入事件

input


案例:模拟 jd 搜索文本框,按 s 获取焦点


  • 需求:
    • 在文档中按 s 让文本框获得焦点,注册键盘事件判断是否按 s 键
    • 文本框获得焦点移除键盘事件
    • 文本框失去焦点注册键盘事件
  • 代码:
    • 元素.focus() 可以让元素获得焦点,同时触发 focus 事件
const search = document.querySelector('#search');

function hanldeFocus(e) {
  if (e.key === 's') {
    search.focus();
    e.preventDefault();
  }
}
document.addEventListener('keydown', hanldeFocus);

search.addEventListener('focus', function () {
  document.removeEventListener('keydown', hanldeFocus);
});

search.addEventListener('blur', function () {
  document.addEventListener('keydown', hanldeFocus);
});

案例:模拟输入快递单号文本框

  • 需求:
    • 文本框输入过程中,上面显示放大的输入内容
  • 代码:


作业

用键盘敲鼓的游戏


许愿墙-拖拽

. 简介

1.1 总则

编码规范用于提供统一编码标准,旨在指导源代码文件的规范,以保证开发团队的协作和系统的后期维护能有效进行,编码规范同时作为代码评审的依据。

1.2 目的

本规范提供一个C#语言编码的统一标准,目的在于在软件开发过程中基于C#语言的代码能够始终在整个开发团队中保持一致。

1.3 适用范围

本规范适用于所有应用C#语言的项目开发与评审活动。

2. 格 式

2.1 缩进

所有的缩进皆为4个空格。对应的括号通常在同一列的位置上。例如:

1)所有的缩进都用"Tab键"形成。

2)所有的if、while和for语句中的语句内容必须用括号括起来,就算只有一个语句。

#001 if (superHero == theTick)

#002 {

#003 System.out.println("Spoon!");

#004 }

2.2 间隔

所有的标识符都必须被空白字符包围。

例如:

#001 int theTick = 5;

#002 if (theTick == 5)

2.3 空行

1)在逻辑代码段之间放置空行来分隔代码段;

2)在两个方法/函数/过程之间以空行来分割;

3)在两个类或接口的定义之间放置空行来分隔;

4)命名空间引入完毕之后放置空行。

2.4 空格的位置

1)在一个关键字和左括号“(”之间。注意:不要在方法名和左括号之间加空格。

2)在参数列表的每个逗号“,”之后。

3)二元操作符前后。注意:一元操作符前后都不加空格。例如:int a = 10; a = a + 1; a++;

4)for语句的每个表达式之间。例如:for (int i = 0; i < 20; i++)…。

2.5 类成员的摆放顺序

class Order

{

1. static fields

2. static properties

3. static methods

4. static constructors

5. fields

6. properties

7. constructors

8. methods

}

9. 必须保持private方法被放置在使用该方法的其他方法之上,而在构造器(constr-uctor)之下,即使该构造器有可能调用这些private方法。

2.6 文件格式

文件注释必须第一个存在。

接着是命名空间的定义。

在命名空间首先应该using指令

再次,是类型的注释;

示例:

#001 /***************************************

#002 文件注释

#003 ****************************************/

#004 using System;

#005 namespace testMail

#006 {

#007

#008 /// <summary>

#009 /// Form1 的摘要说明。

#010 /// </summary>

#011 public class Form1 : System.Windows.Forms.Form

#012 {

#013 }

#014 }

2.7 行最大长度

不要让一行代码的长度超过120个字符,最好是低于80个字符。如果代码开始向右延伸得很长,你就应该考虑把它分割成更多的方法。

断行规则:

l 在逗号的后面;

l 在操作符的前面;

l 断行的起始位置应该比原行表达式的起始位置缩进4个空格。

2.8 括号

使用括号的目的必须是在表达上不但能够标明优先顺序,而且有助于使表达更简单明了。另外,如果某一段代码有可能产生歧义,也需加括号。

3. 命名规则

3.1 基本原则

除了以下几个特例之外,命名时应始终采用完整的英文描述。此外,一般应采用小写字母,但类名、接口名以及任何非初始单词的第一个字母要大写。

注意:不要将同一变量用作多个用途。(如Dataset类型,每查询一次就要声明一个变量。)

l 使用完整的英文描述符;

l 采用适用于该领域的术语;

l 尽量少用缩写,但如果用了,要明智地使用;

l 避免使用长的名字(小于30个字母);

l 避免使用类似的名字,或者仅仅是大小写不同的名字;

l 避免使用下划线;

3.2 大小写

使用下面的三种大写标识符约定:

l Pascal大小写:将标识符的首字母和后面连接的每个单词的首字母都大写。可以对三字符或更多字符的标识符使用Pascal大小写。例如:BackColor

l Camel大小写:标识符的首字母小写,而每个后面连接的单词的首字母都大写。例如:backColor

l 大写:标识符中的所有字母都大写。仅对于由两个或者更少字母组成的标识符使用该约定。例如:System.IO

下表(表1)汇总了大写规则,并提供了不同类型的标识符的示例。

标识符

大小写

示例

Pascal

AppDomain

枚举类型

Pascal

ErrorLevel

枚举值

Pascal

FatalError

事件

Pascal

ValueChange

异常类

Pascal

WebException

注意 总是以 Exception 后缀结尾。

只读的静态字段

Pascal

RedValue

接口

Pascal

IDisposable

注意 总是以 I 前缀开始。

方法

Pascal

ToString

命名空间

Pascal

System.Drawing

参数

Camel

typeName

属性

Pascal

BackColor

受保护的实例字段

Camel

redValue

注意 很少使用。属性优于使用受保护的实例字段。

公共实例字段

Pascal

RedValue

注意 很少使用。属性优于使用公共实例字段。

私有字段

Camel

size

局部变量

Camel

score

方法参数

Camel

age

表2-1

3.3 区分大小写

不要使用要求区分大小写的名称。对于区分大小写和不区分大小写的语言,组件都必须完全可以使用。不区分大小写的语言无法区分同一上下文中仅大小写不同的两个名称。因此,在创建的组件或类中必须避免这种情况。

3.4 缩写

为了避免混淆和保证跨语言交互操作,请遵循有关区缩写的使用的下列规则:

l 不要将缩写或缩略形式用作标识符名称的组成部分。例如,使用GetWindow,而不要使用GetWin。

l 不要使用计算机领域中未被普遍接受的缩写。

l 在适当的时候,使用众所周知的缩写替换冗长的词组名称。例如,用UI作为 User Interface的缩写,用OLAP作为On-line Analytical Processing的缩写。

l 在使用缩写时,对于超过两个字符长度的缩写请使用Pascal w大小写或Camel大小写。例如,使用htmlButton或HtmlButton。但是,应当大写仅有两个字符的缩写,如,System.IO,而不是System.Io。

l 不要在标识符或参数名称中使用缩写。如果必须使用缩写,对于由多于两个字符所组成的缩写请使用Camel大小写,虽然这和单词的标准缩写相冲突。

3.5 名字空间的命名

命名名字空间时的一般性规则是使用公司名称,后跟技术名称和可选的功能与设计,如下所示:CompanyName.TechnologyName[.Feature][.Design]

例如:

Microsoft.Media

Microsoft.Media.Design

嵌套的命名空间应当在包含它的命名空间中的类型上有依赖项。例如,System.Web.UI.-Design中的类依赖于System.Web.UI 中的类。

应当对命名空间使用Pascal大小写,并用句点分隔逻辑组件,如Microsoft.Office.Power-Point中所示。

3.6 类的命名

以下规则概述命名类的规范:

l 使用名词或名词短语命名类。

l 使用 Pascal大小写。

l 少用缩写。

l 不要使用类型前缀,如在类名称上对类使用C前缀。例如,使用类名称FileStr-eam,而不是CFileStream。

l 不要使用下划线字符(_)。

l 有时候需要提供以字母I开始的类名称,虽然该类不是接口。只要I是作为类名称组成部分的整个单词的第一个字母,这便是适当的。例如,类名称IdentitySt-ore是适当的。

l 在适当的地方,使用复合单词命名派生的类。派生类名称的第二个部分应当是基类的名称。例如,ApplicationException对于从名为Exception的类派生的类是适当的名称,原因是ApplicationException 是一种Exception。请在应用该规则时进行合理的判断。例如,Button对于从Control 派生的类是适当的名称。尽管按钮是一种控件,但是将Control作为类名称的一部分将使名称不必要地加长。

示例:

public class FileStream

3.7 接口命名规范

以下规则概述接口的命名规范:

l 用名词或名词短语,或者描述行为的形容词命名接口。例如,接口名称IComp-onent使用描述性名词。接口名称ICustomAttributeProvider使用名词短语。名称IPersistable使用形容词。

l 使用Pascal大小写。

l 少用缩写。

l 给接口名称加上字母 I 前缀,以指示该类型为接口。

l 在定义类/接口对(其中类是接口的标准实现)时使用相似的名称。两个名称的区别应该只是接口名称上有字母 I 前缀。

l 不要使用下划线字符(_)。

3.8 属性[Attribute]命名指南

应该总是将后缀Attribute添加到自定义属性类。以下是正确命名的属性类的示例。

public class ObsoleteAttribute{}

3.9 枚举类型命名规范

l 枚举(Enum)值类型从Enum类继承。以下规则概述枚举的命名指南:

l 对于Enum类型和值名称使用Pascal大小写。

l 少用缩写。

l 不要在Enum类型名称上使用Enum后缀。

l 对大多数Enum类型使用单数名称,但是对作为位域的Enum类型使用复数名称。

l 总是将FlagsAttribute添加到位域Enum类型。

3.10 静态字段命名指南

以下规则概述静态字段的命名指南:

l 使用名词、名词短语或者名词的缩写命名静态字段。

l 使用Pascal大小写。

l 对静态字段名称使用匈牙利语表示法前缀。

l 建议尽可能使用静态属性而不是公共静态字段。

3.11 参数命名规范

以下规则概述参数的命名指南:

l 使用描述性参数名称。参数名称应当具有足够的描述性,以便参数的名称及其类型可用于在大多数情况下确定它的含义。

l 对参数名称使用Camel大小写。

l 使用描述参数的含义的名称,而不要使用描述参数的类型的名称。开发工具将提供有关参数的类型的有意义的信息。因此,通过描述意义,可以更好地使用参数的名称。少用基于类型的参数名称,仅在适合使用它们的地方使用它们。

l 不要使用保留的参数。保留的参数是专用参数,如果需要,可以在未来的版本中公开它们。相反,如果在类库的未来版本中需要更多的数据,请为方法添加新的重载。

l 不要给参数名称加匈牙利语类型表示法的前缀。

示例:

Type GetType(string typeName)

3.12 控件名称缩写列表

缩写的基本原则是取控件类名各单词的第一个字母,如果只有一个单词,则去掉其中的元音,留下辅音。缩写全部为小写。


控件类型

缩写

例子

Label

Lbl

lblNote

TextBox

Txt

txtName

Button

Btn

btnOK

ImageButton

Ib

ibOK

LinkButton

Lb

lbJump

HyperLink

Hl

hlJump

DropDownList

Ddl

ddlList

CheckBox

Cb

cbChoice

CheckBoxList

Cbl

cblGroup

RadioButton

Rb

rbChoice

RadioButtonList

Rbl

rblGroup

Image

Img

imgBeauty

Panel

Pnl

pnlTree

TreeView

Tv

tvUnit

WebComTable

Wct

wctBasic

ImageDateTimeInput

Dti

dtiStart

ComboBox

Cb

cbList

MyImageButton

Mib

mibOK

TreeView

Tv

tvUnit

PageBar

Pb

pbMaster


3.13 方法命名规范

以下规则概述方法的命名指南:

l 使用动词或动词短语命名方法。

l 使用Pascal大小写。

以下是正确命名的方法的实例。

RemoveAll()

3.14 属性命名规范

以下规则概述属性的命名指南:

l 使用名词或名词短语命名属性。

l 使用Pascal大小写。

l 不要使用匈牙利语表示法。

l 考虑用与属性的基础类型相同的名称创建属性。

3.15 事件命名指南

以下规则概述事件的命名指南:

l 对事件处理程序名称使用EventHandler后缀。

l 指定两个名为sender和e的参数。sender参数表示引发事件的对象。sender参数始终是object类型的,即使在可以使用更为特定的类型时也如此。与事件相关联的状态封装在名为e的事件类的实例中。对e参数类型使用适当而特定的事件类。

l 用 EventArgs 后缀命名事件参数类。

l 考虑用动词命名事件。

l 使用动名词(动词的“ing”形式)创建表示事件前的概念的事件名称,用过去式表示事件后。例如,可以取消的Close事件应当具有 Closing事件和Closed事件。不要使用 BeforeXxx/AfterXxx 命名模式。

l 不要在类型的事件声明上使用前缀或者后缀。例如,使用Close,而不要使用 OnClose。

l 通常情况下,对于可以在派生类中重写的事件,应在类型上提供一个受保护的方法(称为 OnXxx)。此方法只应具有事件参数 e,因为发送方总是类型的实例。

4. 注 释

为增加程序的可读性,编写的代码应加上注释,注释的原则为:

l 避免使用装饰物;

l 保持注释的简洁;

l 在写代码之前写注释;

l 注释出为什么做了一些事,而不仅仅是做了什么;

c#语言提供了3种形式的注释:

//text 从//到本行结束的所有字符均作为注释而被编译器忽略。

/* text */ 从/*到*/ 间的所有字符会被编译器忽略。

项目

注释哪些部分

实参/ 参数

参数类型

参数用来做什么

任何约束或前提条件

字段/属性

字段描述

注释所有使用的不同变量

可见性决策

类的目的

类的开发/维护历史

注释出采用的不同变量

版权信息

编译单元

每一个类/类内定义的接口,含简单的说明文件名和/或标识信息版权信息

接口

目的

它应如何被使用以及如何不被使用

局部变量

用处/目的

成员函数注释

成员函数做什么以及它为什么做这个

哪些参数必须传递给一个成员函数

成员函数返回什么

任何由某个成员函数抛出的异常

成员函数是如何改变对象的

包含任何修改代码的历史

如何在适当情况下调用成员函数的例子

成员函数

内部注释

控制结构

代码做了些什么以及为什么这样做

局部变量

表3-1

可以轻松地在C#代码结构中添加文档注释,并通过代码注释Web报告进行查看。显示在代码注释Web报告中的注释和XML标记以注释语法 /// 开头。通常,文档注释注释在用户定义的类型(如类、结构或接口)、成员(如字段、事件、属性或方法)或命名空间声明之前输入。

例如:

在C#代码中添加文档注释:

在编辑器中打开.cs文件。

切换到代码视图。

输入///,后面可跟任何 XML 标记或文本字符串。如果在定义之前的行上输入///,则编辑器将创建文档注释模板并填入参数和其他信息。

例如,文件class1.cs 中,在public Class1()前可输入下列信息:


///<summary>

///summary description

///</summary>

///<remarks>

///This is a test.

///</remarks>


在Class1的代码结构中,<summary></summary>标记中所列出的信息出现在“说明”列。 <remarks></remarks>标记中所列出的信息出现在备注部分的class1详细信息页中。

5. 编 码

一种提高代码可读性的方法是给代码分段,在代码块内让代码缩进。所有在括号 {} 之内的代码,构成一个块。基本思想是,块内的代码都应统一地缩进去一个单位。

c#的约定:开括号放在块的所有者所在行的下面并缩进一级,闭括号也应缩进一级。

在代码中使用空白。将代码分为一些小的、容易理解的部分,可以使它更加可读。建议采用一个空行来分隔代码的逻辑组,例如控制结构,采用两个空行来分隔成员函数定义。

遵循30秒条法则。其他的程序员应能在少于30秒钟的时间内完全理解你的成员函数,理解它做什么,为什么这样做,它是怎样做的。一个好的经验法则是:如果一个成员函数一个屏幕装不下,那么它就很可能太长了。

写短小单独的命令行。每一行代码只做一件事情。应使代码尽量容易理解,从而更容易维护和改进。正如同一个成员函数应该并且只能做一件事一样,一行代码也只应做一件事情。

应让代码在一个屏幕内可见。也不应向右滚动编辑窗口来读取一整行代码,包括含有行内注释语句的代码。

6. 不要使用的结构

6.1 “do…while”

不要用do…while循环,用while()循环。

6.2 “return”

不要在一个方法的中间使用“return”,“return”只能出现在一个方法的末尾。

原因:在方法的中间使用“return”会给重构(今后将方法拆分成几个更小的方法)带来困难;而且它会迫使开发者不得不为该方法考虑多于一个的出口点。

6.3 “continue”

绝不要用“continue”。

原因:“continue”会给将来把一个结构拆分成几个更小的结构或方法带来许多困难;而且她也会迫使开发者不得不为该结构考虑多于一个的结束点。

6.4 “break”

“break”只能用于转换状态(switch statement)的控制。

原因:在转换状态控制之外的情况下使用break,会给将来把一个结构拆分成几个更小的结构或方法带来许多困难;而且她也会迫使开发者不得不为该结构考虑多于一个的结束点。

7. 不要混合使用递增运算符和递减运算符

不要混合使用递增运算符和递减运算符,

原因:在方法调用或是数学运算中混合使用递增运算符(或递减运算符)会造成欠经验的程序员阅读的困难。

所以,最好在递增运算符(或递减运算符)之间加上额外的行。

变量初始化

最好总是在每个变量声明的时候就马上进行初始化。

最好只在需要的时候再声明(declare)一个变量,不然的话会影响代码的执行效果。

示例:

#001 int secondWide = 12 ;

#002 int firstWide = doFoo(20 ,secondWide );

#003 doBar(firstWide ,secondWide );

#004 int totalWide = firstWide + secondWide ; //很好!

8. 不要使用数字/字符

程序中应尽可能少使用数字/字符,尽可能定义静态变量来说明该数字/字符的含义,程序中需要赋值或比较时,使用前面定义的静态变量。

例如:date1.get(1),而应该date1.get(MyDate.YEAR)。

例外:

循环控制:例如:

#001 for (int i = 0; i < 20; i++);

9. 重复代码块

尽量避免使用重复的代码块,如果一个类中出现2个以上相同功能的代码块,应该将它作为类的私有方法;如果你认为多个类中都可以用到,应该将它作为util中的方法,并通知做工具类的人去写这个方法。

10. 范围

原则上类的成员变量必须是总是private,尽量少用protected和public,但以下情况例外:

l 内部类的成员变量(可以为public);

l 子类可继承的基类成员变量(可以为protected);

l 并发控制中的信号变量(可以为public)。

11. 异常处理

每个页面加上排除异常代码,保证程序在发生异常后能继续正常运行,采用try、catch、finally结构,如:

#001 try

#002 {

#003 程序体………

#004 }

#005 catch(xxxException e)

#006 {

#007 程序体……

#008 }

#009 catch(xxxException e)

#010 {

#011 程序体………

#012 }

#013 catch(xxxException e)

#014 {

#015 程序体………

#016 }

#017 finally

#018 {

#019 程序体………

#020 }


说明:Try…Catch…finally异常处理模式是很消耗资源的,所以所加位置要慎重,避免多余的异常处理。