HTML5 生态体系
重要通知
HTML5是HTML最新的修订版本,2014年10月由万维网联盟(W3C)完成标准制定,2022年微软放弃IE支持后,基本可以忽略浏览器兼容性问题。
HTML5基本概况
- HTML标签大全:https://www.runoob.com/tags/ref-byfunc.html
- https://webplatform.github.io/
- W3C中国:http://www.chinaw3c.org/
- 浏览器兼容性检测: https://caniuse.com/
W3C标准
- https://www.w3.org/
- https://www.w3.org/TR/
- https://www.w3.org/TR/wai-aria-practices-1.1/
- W3C Web 性能工作组
- https://www.w3.org/webperf/
- https://github.com/w3c/web-performance/
- W3C on GitHub:https://w3c.github.io/
W3C验证
- 网页HTML在线验证:http://validator.w3.org
- 网页CSS在线验证:http://jigsaw.w3.org/css-validator
- 网页链接在线验证:http://validator.w3.org/checklink
W3C规范
网页主要有三部分组成:元素-结构(Structure)、样式-表现(Presentation)、脚本-行为(Behavior)
- XHTML 1.0 提供了三种DTD声明可供选择
过渡的(Transitional):要求非常宽松的DTD,它允许你继续使用HTML4.01的标识(但是要符合xhtml的写法)。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
- 严格的(Strict):要求严格的DTD,你不能使用任何表现层的标识和属性,例如
<br>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
- 框架的(Frameset):专门针对框架页面设计使用的DTD,如果你的页面中包含有框架,需要采用这种DTD。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
<DOCTYPE(document type)>
<style type=“text/css”></style>
<script language="javascript" type="text/javascript"></script>
<meta http-equiv=“Content-Type” content=“text/html; charset=gb2312” />
HTML 字符实体
----------------------------------------------------------------------------------
显示结果 描述 实体名称 实体编号
----------------------------------------------------------------------------------
空格  
< 小于号 < <
> 大于号 > >
& 和号 & &
" 引号 " "
' 撇号 ' (IE不支持) '
¢ 分(cent) ¢ ¢
£ 镑(pound) £ £
¥ 元(yen) ¥ ¥
€ 欧元(euro) € €
§ 小节 § §
© 版权(copyright) © ©
® 注册商标 ® ®
™ 商标 ™ ™
× 乘号 × ×
÷ 除号 ÷ ÷
----------------------------------------------------------------------------------
浏览器内核与前缀
- -webkit- WebKit内核 Chrome(谷歌浏览器) 、Safari(苹果浏览器)
- -moz- Gecko内核 Firefox(火狐浏览器)
- -ms- Trident内核 IE(IE浏览器)
- -o- Presto内核 Opera(欧朋浏览器)
排版引擎三种模式
- 怪异模式(Quirks mode):排版会模拟 Navigator 4 与 Internet Explorer 5 的非标准行为。
- 接近标准模式(Almost standards mode):只有少数的怪异行为被实现。
- 标准模式(Standards mode):行为即(但愿如此)由 HTML 与 CSS 的规范描述的行为。
<!DOCTYPE html>
<html>
<head>
<meta charset=UTF-8>
<title>Hello World!</title>
</head>
<body>
</body>
</html>
- IE6的触发:在XHTML的DOCTYPE前加入XML声明
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
- IE7的触发:在XML声明和XHTML的DOCTYPE之间,加入HTML注释
<?xml version="1.0" encoding="utf-8"?>
<!-- ... and keep IE7 in quirks mode -->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
- IE6和IE7都可以触发的:在HTML4.01的DOCTYPE文档头部,加入HTML注释
<!-- quirks mode --> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
- 在页面顶部加 ,将触发"怪异模式"
- 没有使用DTD声明或者使用HTML4以下(不包括HTML4)的DTD声明时,基本上所有的浏览器都是使用quirks mode呈现
MVVM模式
MVVM模式,即Model,即数据模型,业务逻辑的操作通过直接控制数据状态;View,即视图UI界面;ViewModel,即视图模型,视图UI根据数据变化而完成自动布局。
三层结构
分别是结构层、表示层、行为层。其中,结构层的实现依赖于HTML。表示层的实现依赖于CSS。行为层的实现依赖于JS。
优雅降级
优雅降级(Graceful degradation)是一种设计理念,其核心是尝试构建可在最新浏览器中运行的现代网站/应用程序,而作为降级体验,在低版本浏览器中仍然提供必要的内容和功能。
- Polyfill可用于使用 JavaScript 构建缺少的功能,但应尽可能提供样式和布局等功能的可接受替代方案,例如使用 CSS 级联或 HTML 回退行为。
渐进增强
渐进增强(Progressive enhancement)是一种设计理念,其核心是为尽可能多的用户提供基本内容和功能,同时进一步为现代化浏览器用户提供最佳体验,运行所有需要的代码。
- 特性检测通常用于确定浏览器是否可以处理高级内容。
MVVM模式
MVVM,即Model-View-ViewModel,
- VM:ViewModel,即视图模型,视图UI根据数据变化而完成自动布局。
- M:Model,即数据模型,业务逻辑的操作通过直接控制数据状态;
- V:View,即视图UI界面;
浏览器兼容方案
检测某个属性是否被浏览器支持:https://caniuse.com/
各个浏览器版本前缀
----------------------------------------------------------------------------------
Chrome Egde Firefox Opera
----------------------------------------------------------------------------------
-webkit- -ms- -moz- -o-
----------------------------------------------------------------------------------
主流浏览器引擎前缀
-webkit- (谷歌,Safari,新版Opera浏览器,以及几乎所有iOS系统中的浏览器(包括 iOS 系统中的火狐浏览器);基本上所有基于WebKit 内核的浏览器) -moz- (火狐浏览器) -o- (旧版Opera浏览器) -ms- (IE浏览器 和 Edge浏览器)
IE浏览器
# 正则
(>) 大于 - gt
(<) 小于 - lt
(>=) 大于等于 - gte
(<= ) 小于等于 - lte
# IE
<!--[if IE]>
在IE浏览器显示
<![endif]-->
# IE6 | IE7 | IE8 | IE9 | IE10 | IE11
<!--[if IE 6]>
在IE6浏览器显示
<![endif]-->
# 小于IE9显示
<!--[if lte IE 8]>
在IE6浏览器显示
<![endif]-->
# 只在IE8上不生效
<!--[if ! IE 8]>
非IE8浏览器显示
<![endif]-->
# 非IE
<!--[if !IE]>
在非IE浏览器显示
<![endif]-->
IE浏览器各版本 CSS hack 对照表
--------------------------------------------------------------------------------------------------------------
hack 写法 实例 IE6(S) IE6(Q) IE7(S) IE7(Q) IE8(S) IE8(Q) IE9(S) IE9(Q) IE10(S) IE10(Q)
--------------------------------------------------------------------------------------------------------------
* *color 青色 Y Y Y Y N Y N Y N Y
+ +color 绿色 Y Y Y Y N Y N Y N Y
- -color 黄色 Y Y N N N N N N N N
_ _color 蓝色 Y Y N Y N Y N Y N N
# #color 紫色 Y Y Y Y N Y N Y N Y
\0 color:red\0 红色 N N N N Y N Y N Y N
\9\0 color:red\9\0 粉色 N N N N N N Y N Y N
!important color:blue !important; color:green; 棕色 N N Y N Y N Y N Y Y
--------------------------------------------------------------------------------------------------------------
低版本浏览器提示
具体参考页面 /home/meta/examples/brower-low-version.html
HTMLElement.prototype.appendHTML = function (html) {
var divTemp = document.createElement("div");
var nodes = null;
//文档片段,一次性append,提高性能
var fragment = document.createDocumentFragment();
divTemp.innerHTML = html;
nodes = divTemp.childNodes;
for (var i = 0; i < nodes.length; i++) {
fragment.appendChild(nodes[i].cloneNode(true));
}
this.appendChild(fragment);
nodes = null;
fragment = null;
};
// 是否为低版本浏览器
let isLowBrower = false;
try {
isLowBrower = false;
console.info(Proxy.toString());
} catch (e) {
isLowBrower = true;
}
if (isLowBrower) {
var browerHTML = ` <div class="define-low-brower-container">
<img class="define-low-brower-img" src="/images/404.png" alt="">
<h3 class="define-low-brower-h3">出错啦!</h3>
<p class="define-low-brower-p">啊噢...浏览器版本太低,请尝试升级浏览器!</p>
<div class="define-low-brower-type">
<a href="https://www.google.com/chrome/" target="_blank">
<img class="define-low-brower-chrome" src="/images/chrome.svg" alt="">
<span class="define-low-brower-chrome-download">chrome浏览器</span>
</a>
<a href="http://www.firefox.com.cn/" target="_blank">
<img class="define-low-brower-firefox" src="/images/firefox.svg" alt="">
<span class="define-low-brower-firefox-download">Firefox浏览器</span>
</a>
</div>
</div>`;
document.body.appendHTML(browerHTML);
}
meta标签
<meta>
标签提供了 HTML 文档的元数据。元数据不会显示在客户端,但是会被浏览器解析。
charset
定义文档的字符编码。
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
content
定义与 http-equiv 或 name 属性相关的元信息。
<!--忽略数字自动识别为电话号码-->
<meta content="telephone=no" name="format-detection" />
<!--忽略识别邮箱-->
<meta content="email=no" name="format-detection" />
http-equiv
把 content 属性关联到 HTTP 头部。
- content-type:
- default-style:
- refresh:
<!--页面重定向和刷新-->
<meta http-equiv="refresh" content="0;url=" />
<meta http-equiv="X-UA-Compatible" content="ie=edge,chorome=1">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<!--优先使用 IE 最新版本和 Chrome-->
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<!-- 关于X-UA-Compatible -->
<meta http-equiv="X-UA-Compatible" content="IE=6"><!-- 使用IE6 -->
<meta http-equiv="X-UA-Compatible" content="IE=7"><!-- 使用IE7 -->
<meta http-equiv="X-UA-Compatible" content="IE=8"><!-- 使用IE8 -->
<!--禁止浏览器从本地计算机的缓存中访问页面内容-->
<meta http-equiv="Pragma" content="no-store">
<!--网页到期时间-->
<meta http-equiv="expires" content="Wed, 20 Jun 2020 22:33:00 GMT" />
<meta http-equiv="Cache-control" content="no-cache">
<!--
no-store,请求和响应的信息都不应该被存储在对方的磁盘系统中;
no-cache,浏览器和缓存服务器都不应该缓存页面信息[协商缓存];
public,浏览器和缓存服务器都可以缓存页面信息;
must-revalidate,对于客户机的每次请求,代理服务器必须想服务器验证缓存是否过时;
-->
<meta http-equiv="Cache" content="no-cache">
<!--转码申明:用百度打开网页可能会对其进行转码(比如贴广告),避免转码可添加如下meta-->
<meta http-equiv="Cache-Control" content="no-siteapp" />
<!--添加`http-equiv`属性和`content`属性来配置`gzip`压缩-->
<meta http-equiv="Content-Encoding" content="gzip">
name
把 content 属性关联到一个名称。
- application-name:网页中所运行的应用程序的名称。
- author:文档作者的名字。
- description:一个不超过 150 个字符且能准确反映网页内容的描述标签。
- keywords:与页面内容相关的关键词,使用逗号分隔。
- generator:生成此页面的软件的标识符(identifier)。
- referrer:控制由当前文档发出的请求的 HTTP Referer 请求头。
<meta name="application-name" content="应用程序名称" />
<meta name="author" content="作者名字" />
<meta name="keywords" content="关键词,关键词">
<meta name="description" content="描述信息,150字内" />
<meta name="generator" content="标识符">
<meta name="referrer" content="" />
<!--控制由当前文档发出的请求的 HTTP Referer 请求头
no-referrer 不发送 HTTP Referer 请求头。
origin 只发送当前文档的 origin。
no-referrer-when-downgrade 如果请求目标与当前页面一样安全或者更加安全(HTTP(S)→HTTPS),则发送完整 URL;如果请求目标更加不安全(HTTPS→HTTP),则不发送 referrer。这是默认行为。
origin-when-cross-origin 对同源请求发送完整 URL(不含 URL 参数),其他情况下,只发送 origin。
same-origin 对同源请求发送完整 URL(不含 URL 参数),其他情况下,请求不包含 referrer 请求头。
strict-origin 如果请求目标与当前页面一样安全或者更加安全(HTTP(S)→HTTPS),则发送 origin;如果请求目标更加不安全(HTTPS→HTTP),则不发送 referrer。
strict-origin-when-cross-origin 对同源请求发送完整 URL(不含 URL 参数);其他情况下,如果请求目标与当前页面一样安全或者更加安全(HTTP(S)→HTTPS),则发送 origin;如果请求目标更加不安全(HTTPS→HTTP),则不发送 referrer。
unsafe-URL 对同源请求和跨源请求发送完整 URL(不含 URL 参数)。
-->
<!--览器内核控制: 双内核(webkit和Trident),webkit内核高速浏览,IE内核兼容网页和旧版网站-->
<meta name="renderer" content="webkit | ie-comp | ie-stand">
<meta name="msapplication-TileColor" content="#000" /> <!-- Windows 8 磁贴颜色 -->
<meta name="msapplication-TileImage" content="icon.png" /> <!-- Windows 8 磁贴图标 -->
<!--站点适配:主要用于PC-手机页的对应关系-->
<meta name="mobile-agent" content="format=[wml|xhtml|html5]; url=url">
<!--
[wml|xhtml|html5]根据手机页的协议语言,选择其中一种;
url="url" 后者代表当前PC页所对应的手机页URL,两者必须是一一对应关系。
-->
<!--移动网站-->
<meta name="viewport" content="width=device-width, initial-scale=1.0,maximum-scale=1.0, user-scalable=no" />
<!--
`width=device-width` 会导致 iPhone 5 添加到主屏后以 WebApp 全屏模式打开页面时出现黑边
width:宽度(数值 / device-width)(范围从200 到10,000,默认为980 像素)
height:高度(数值 / device-height)(范围从223 到10,000)
initial-scale:初始的缩放比例 (范围从>0 到10)
minimum-scale:允许用户缩放到的最小比例
maximum-scale:允许用户缩放到的最大比例
user-scalable:用户是否可以手动缩 (no,yes)
-->
<!--IOS9-->
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<script type="text/javascript">
// 注意:iOS10以后版本不接受meta标签,可以通过js监听手势控制来实现禁止页面缩放
document.addEventListener('gesturestart', function (event) {
event.preventDefault()
});
</script>
<!-- 启用 WebApp 全屏模式 已废弃 -->
<!--隐藏状态栏/设置状态栏颜色:只有在开启WebApp全屏模式时才生效。content的值为default | black | black-translucent-->
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<!--添加到主屏后的标题-->
<meta name="apple-mobile-web-app-title" content="标题" />
<!--添加智能 App 广告条 Smart App Banner:告诉浏览器这个网站对应的app,并在页面上显示下载banner-->
<meta name="apple-itunes-app" content="app-id=myAppStoreID, affiliate-data=myAffiliateData, app-argument=myURL">
<!-- 针对手持设备优化,主要是针对一些老的不识别viewport的浏览器,比如黑莓 -->
<meta name="HandheldFriendly" content="true">
<!-- 微软的老式浏览器 -->
<meta name="MobileOptimized" content="320">
<!-- uc强制竖屏 -->
<meta name="screen-orientation" content="portrait">
<!-- QQ强制竖屏 -->
<meta name="x5-orientation" content="portrait">
<!-- UC强制全屏 -->
<meta name="full-screen" content="yes">
<!-- QQ强制全屏 -->
<meta name="x5-fullscreen" content="true">
<!-- UC应用模式 -->
<meta name="browsermode" content="application">
<!-- QQ应用模式 -->
<meta name="x5-page-mode" content="app">
<!-- windows phone 点击无高光 -->
<meta name="msapplication-tap-highlight" content="no">
<!--指定与当前文档兼容的一种或多种配色方案-->
<meta name="color-scheme" content="dark light">
<!--禁止搜索引擎爬取--><!--方法一:robots.txt 方法二:meta标签-->
<meta name="Robots" contect="all | none | index | noindex | follow | nofollow">
<!--
设定为all:文件将被检索,且页面上的链接可以被查询;
设定为none:文件将不被检索,且页面上的链接不可以被查询;
设定为index:文件将被检索;
设定为follow:页面上的链接可以被查询;
设定为noindex:文件将不被检索,但页面上的链接可以被查询;
设定为nofollow:文件将不被检索,页面上的链接可以被查询。
-->
<!--通知搜索引擎多少天访问一次-->
<!--版本声明-->
<Meta name="Copyright" Content="本页版权归焰光实验室所有。All Rights Reserved">
property
富媒体对象,利于SNS提取信息
- Metadata:https://ogp.me/
<!--Open Graph Protocol:富媒体对象,利于SNS提取信息-->
<meta property="og:type" content="video"/>
<meta property="og:title" content="五月天_突然好想你MV现场版"/>
<meta property="og:image" content="http://g1.ykimg.com/0100641F464A ... 9-76EA-E5E20A1887C4"/>
<meta property="og:url" content="http://v.youku.com/v_show/id_XMTIyMTY5NzMy.html"/>
<meta property="og:videosrc" content="http://player.youku.com/player.p ... AutoPlay=true/v.swf"/>
<meta property="og:width" content="500" />
<meta property="og:height" content="416" />
<!--图片-->
<meta property="og:image" content="https://example.com/ogp.jpg" />
<meta property="og:image:secure_url" content="https://secure.example.com/ogp.jpg" />
<meta property="og:image:type" content="image/jpeg" />
<meta property="og:image:width" content="400" />
<meta property="og:image:height" content="300" />
<meta property="og:image:alt" content="A shiny red apple with a bite taken out" />
<!--视频-->
<meta property="og:video" content="https://example.com/movie.swf" />
<meta property="og:video:secure_url" content="https://secure.example.com/movie.swf" />
<meta property="og:video:type" content="application/x-shockwave-flash" />
<meta property="og:video:width" content="400" />
<meta property="og:video:height" content="300" />
<!--音频-->
<meta property="og:audio" content="https://example.com/sound.mp3" />
<meta property="og:audio:secure_url" content="https://secure.example.com/sound.mp3" />
<meta property="og:audio:type" content="audio/mpeg" />
link标签
<link>
标签定义文档与外部资源的关系。
href
定义被链接文档的位置。
<link rel="stylesheet" href="style.css">
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon">
media
规定被链接文档将显示在什么设备上。
<link rel="stylesheet" media="mediatype and|not|only (expressions)" href="print.css">
<link rel="stylesheet" media="screen and (min-width: 900px)" href="widescreen.css">
rel
rel 是 relationship的英文缩写,定义当前文档与被链接文档之间的关系。
<!--充分利用浏览器的空闲时间去最优化调度用户浏览网页所依赖的资源:预解析 • 预连接 • 预获取 • 预渲染 • 预加载-->
<!--预解析:仅预解析 DNS(域名 → IP)-->
<link rel="dns-prefetch" href="url">
<!--预连接:预解析 DNS + 建立 TCP 连接 + TLS 握手-->
<link rel="preconnect" crossorigin href="url">
<!--预获取 | prefetch(预测页面)subresource(当前页面)-->
<!--使用场景:未来操作可能使用资源(提前缓存辅助导航)-->
<!--加载时机和优先级:延迟加载,低优先级-->
<!--缓存行为:两者都会被缓存,但`preload`的资源如果未被使用可能会有警告-->
<!--用于预加载下一个页面可能需要的资源,比如用户可能访问的下一个页面的资源。这属于跨页面的优化,对当前页面没有直接影响,但能提升后续导航的速度。-->
<!--通常用于非关键资源,浏览器会在空闲时加载这些资源,优先级较低。-->
<link rel="prefetch" crossorigin as="script" href="url"> <link rel="subresource" href="url">
<!--预渲染 | prerender(大众浏览器)next(Firefox特有属性)-->
<link rel="prerender" href="url"> <link rel="next" href="url">
<!--预加载:告诉浏览器当前页面肯定会用到的资源,需要优先加载。比如关键的字体、图片或脚本。这会提升当前页面的性能,因为资源会被提前加载,减少渲染阻塞。-->
<!--使用场景:当前页面渲染必需资源(关键资源优先加载)-->
<!--加载时机和优先级:立即加载,高优先级-->
<!--缓存行为:两者都会被缓存,但`preload`的资源如果未被使用可能会有警告-->
<link rel="preload" as="指定资源类型:audio document embed fetch font image object script style track worker video" href="">
<!--当同时拥有web网站与wap网站时,向搜索引擎声明该两个站点为对应关系,避免搜索引擎重复收录,导致违规-->
<!--PC网站-->
<link rel="alternate" href="wap.fujinhuo.com">
<!--wap网站-->
<link rel="canonical" href="www.fujinhuo.com">
<link rel="alternate" href="atom.xml" type="application/atom+xml" title="Atom">
<link rel="alternate" href="rss.xml" type="application/rss+xml" title="RSS">
<!--导入页面-->
<link rel="import" href="component.html">
<link rel="manifest" href="manifest.json">
<link rel="stylesheet" href="print.css" media="print">
<!--IOS ICON-->
<link rel="apple-touch-icon" href="favicon.png">
<link rel="apple-touch-icon" sizes="72x72" href="/icon-72x72.png" />
<link rel="apple-touch-icon" sizes="114x114" href="/icon-114x114.png" />
script标签
<script>
标签定义脚本与外部资源的关系。
type
- at least in HTML 4.01 and XHTML 1(.1), the type attribute for
<script>
elements is required. - In HTML 5, type is no longer required.
<script type="text/javascript" src=""></script>
<script type="text/ecmascript" src=""></script>
<script type="application/ecmascript" src=""></script>
<script type="application/javascript" src=""></script>
<script type="text/vbscript" src=""></script>
<!-- 只有基于 Chromium 的浏览器支持导入映射表 -->
<script type="importmap">
{
"imports": {
"lodash": "/node_modules/lodash-es/lodash.js"
}
}
</script>
<!--模块化-->
<script type="module" src="/main.js"></script>
<script type="module">
import {addTextToBody} from './utils.js';
addTextToBody('Modules are pretty cool.');
// 动态加载模块
import('/modules/myModule.mjs')
.then((module) => {
// Do something with the module.
});
</script>
仅加载但不执行(不是延迟执行)
有时候需要处理大文件加载问题,可以提前加载,但是不能执行,只有用到的时候再执行。
方法一:利用
<link rel="preload">
(推荐)方法二:通过 fetch + Blob(自定义缓存)
方法三:Service Worker 拦截(高级场景)
代码示例
// 预加载脚本但不执行
export function preloadScriptHelper(url: string) {
// 判断脚本是否已经存在
const scripts = document.getElementsByTagName('link');
const currentScripts = Object.values(scripts).filter((item) => item.href === url);
if (!url || currentScripts.length) {
return true;
}
const link = document.createElement('link');
link.rel = 'preload';
link.as = 'script';
link.href = url;
document.head.appendChild(link);
return true;
}
defer属性
<script src="url" defer>
限于外部脚本有效
- 加载机制:遵循资源从上而下的同步加载顺序机制,解析到
<script src="url" defer>
语句即可开始下载。 - 执行时机:延迟执行,延迟到所有脚本执行完毕后,即文档解析完成,即在DOMContentLoaded事件完成时前执行。
<script defer src=""></script>
<!--依赖defer脚本加载后执行完成的内容-->
document.addEventListener("DOMContentLoaded", function(event) {
console.log("defer");
});
async属性
<script src="url" async>
限于外部脚本有效
- 加载机制:异步加载,不阻塞其他的JS脚本加载。
- 执行时机:声明async的脚本加载完成后就开始执行。
<script async src=""></script>
<!--依赖async脚本加载后执行完成的内容-->
window.addEventListener('load', (event) => {
console.log("async");
});
- 研究gtag的引入机制,既使用了async,也可以同步形式调用。
gtag.js很大可能实现是用过监听dataLayer属性来实现gtag方法。
<script async src="https://www.googletagmanager.com/gtag/js?id=AW-527467782"></script>
<script type="text/javascript">
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag("js", new Date());
gtag("config", "AW-527467782");
</script>
- async与defer的优先级
如果同时使用 async 和 defer 属性,async 属性会优先执行,defer 属性会被忽略。
nomodule
<!--使用 nomodule 属性向后兼容-->
<script nomodule src="fallback.js"></script>
<script>
属性参数
获取- index.html
<!--未声明type="module"属性时使用document.currentScript-->
<script
async
src="https://test.ysunlight.com/List/test/index.js"
data-api-key="data-api-key-md5"
></script>
<!--声明type="module"属性时使用document.querySelector(`script[src="${import.meta.url}"]`)-->
<script
type="module"
data-api-key="data-api-key-module"
src="https://test.ysunlight.com/List/test/module.js"
></script>
- index.js
const script = document.currentScript;
console.info('script: ', script.getAttribute('data-api-key'));
- module.js
const script = document.querySelector(`script[src="${import.meta.url}"]`);
console.info('script: ', script.getAttribute('data-api-key'));
脚本自定义加载
function doLoadScriptCallback(url, options = {}) {
const async = options.async || false;
const type = options.type || "text/javascript";
const crossorigin = options.crossorigin || "crossorigin";
const referrerpolicy = options.referrerpolicy || "no-referrer";
const attrs = options.attrs || {};
const scripts = document.getElementsByTagName('script');
const links = Array.from(scripts).map(function (item) { return item.src });
return new Promise(function(resolve) {
// 脚本已经存在
if (links.includes(url)) return resolve(true);
// 创建脚本对象
const script = document.createElement("script");
script.type = type;
script.async = async;
script.crossorigin = crossorigin;
script.referrerpolicy = referrerpolicy;
// 遍历属性
const entries = Object.entries(attrs);
entries.forEach((item) => {
script.setAttribute(item[0], item[1]);
});
script.src = url;
document.body.appendChild(script);
// IE
if (script.readyState) {
script.onreadystatechange = function () {
if (
script.readyState == "loaded" ||
script.readyState == "complete"
) {
script.onreadystatechange = null;
resolve(true);
} else {
resolve(false);
}
};
}
// Others: Firefox, Safari, Chrome, and Opera
else {
script.onload = function () {
resolve(true);
};
script.onerror = function () {
resolve(false);
}
}
});
}
异常错误问题
- 同一个
<script>
块级上下文异常错误
<script type="text/javascript">
console.info("INDEX: ", INDEX); // Uncaught ReferenceError: INDEX is not defined
console.info("打印日志-001"); // 上一行代码异常报错,因此中止执行
</script>
<script type="text/javascript">
console.info("打印日志-002"); // 不受到上一个`<script>`块级上下文异常错误的影响,可以继续执行
</script>
脚本加载机制案例
- index.js
const INDEX = {
name: "INDEX"
};
- a.js
const A = {
name: "A"
};
- b.js
const B = {
name: "B"
};
- c.js
const C = {
name: "C"
};
- index.html
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>脚本加载机制</title>
</head>
<body></body>
<script async src="index.js"></script>
<script defer src="a.js"></script>
<script src="b.js"></script>
<script src="c.js"></script>
<script type="text/javascript">
console.info("INDEX: ", INDEX); // 可能会报错,不一定会100%报错,因为async声明不能决定该脚本什么时候加载完成
console.info("A: ", A);
console.info("B: ", B);
console.info("C: ", C);
</script>
<script type="module">
document.addEventListener("DOMContentLoaded", function (event) {
console.info("DOMContentLoaded A: ", A); // 可能报错,不一定会100%报错,因为该事件触发时,可能async声明的脚本还没加载
console.info("DOMContentLoaded INDEX: ", INDEX);
});
window.addEventListener("load", (event) => {
console.info("load INDEX: ", INDEX);
});
</script>
</html>
Element 元素
块级元素
<!--块级元素-->
<header> <footer> <section> <main> <aside> <article> <nav> <blockquote> <hgroup> <fieldset>
<address> <figcaption> <figure> <output> <noscript>
<body> <div> <p> <ul> <li> <ol> <dl> <dt> <dd> <table> <tr> <form> <pre> <tfoot>
<h1> <h2> <h3> <h4> <h5> <h6> <hr> <audio> <video> <canvas>
内联元素
<!--内联元素:设置width height等属性无效-->
<abbr> <cite> <code> <dfn> <kbd> <bdo>
<em> <span> <i> <a> <br> <img> <button> <input> <label> <select> <textarea>
<object> <small> <b> <strong> <samp> <var> <map> <sup> <sub> <q>
``` ts ```
空元素
<!--空元素-->
<img> <input> <link> <meta> <param> <source> <track> <br>
<area> <base> <br> <col> <colgroup> <command> <embed> <hr>
全局属性
全局属性是所有 HTML 元素共有的属性;它们可以用于所有元素,即使属性可能对某些元素不起作用。
<Element
accesskey
autocapitalize
autofocus
class
contenteditable
data-*
dir
draggable
enterkeyhint
exportparts
hidden
id
inert
inputmode
is
itemid
itemprop
itemref
itemscope
itemtype
lang
nonce
part
popover
slot
spellcheck
style
tabindex
title
translate
/>
<input
autocomplete="email"
spellcheck="false"
autocapitalize="off"
aria-required="true"
/>
HTML5语义化标签
HTML5语义化标签,即相关标签具有自己的特定语义,网页使用恰当的语义标签,不仅促使网页结构更加布局合理,而且有利于SEO检索。
布局语义标签
<header>
:定义了文档的头部区域。<section>
:定义了文档的某个区域。<footer>
:定义一个文档底部。<aside>
:定义其所处内容之外的内容。<nav>
:定义导航链接。<main>
:定义文档的主体部分。<article>
:定义一个文章内容。<figure>
:用于对元素进行组合。
列表标签
<ul>
:定义一个无序列表<ol>
:定义一个有序列表<li>
:定义一个列表项<dl>
:定义一个定义列表<dt>
:定义一个定义定义列表中的项目。<dd>
:定义定义列表中项目的描述。
<table>
标签
表格<table>
:定义一个表格<caption>
:定义表格标题。<th>
:定义表格中的表头单元格。<tr>
:定义表格中的行。<td>
:定义表格中的单元。<thead>
:定义表格中的表头内容。<tbody>
:定义表格中的主体内容。<tfoot>
:定义表格中的表注内容(脚注)。<col>
:定义表格中一个或多个列的属性值。<colgroup>
:定义表格中供格式化的列组。
<style type="text/css">
table {
border-collapse: collapse; /* 设置表格的边框是否被合并为一个单一的边框 */
}
</style>
<table>
<thead>
<tr>
<td>1</td>
<td>1</td>
<td>1</td>
<td colspan="2">合并列</td>
</tr>
<tr>
<td rowspan="2">合并行</td>
<td>1</td>
<td>1</td>
<td>1</td>
<td>1</td>
</tr>
<tr>
<td>1</td>
<td>1</td>
<td>1</td>
<td>1</td>
</tr>
<tr>
<td>1</td>
<td>1</td>
<td>1</td>
<td>1</td>
<td>1</td>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>1</td>
<td>1</td>
<td>1</td>
<td>1</td>
</tr>
<tr>
<td>1</td>
<td>1</td>
<td>1</td>
<td>1</td>
<td>1</td>
</tr>
<tr>
<td>1</td>
<td>1</td>
<td>1</td>
<td>1</td>
<td>1</td>
</tr>
</tbody>
</table>
<form>
标签
表单<form>
:定义一个 HTML 表单,用于用户输入。<input type="file">
: https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/input/file
<input>
定义一个输入控件。
- 限制输入内容
<style type="text/css">
/* Chrome, Safari, Edge, Opera */
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
/* Firefox */
input[type="number"] {
-moz-appearance: textfield;
}
input::-webkit-input-placeholder { /*webkit*/
color: #ccc;
}
input::-moz-input-placeholder { /*Firefox 19+*/
color: #ccc;
}
input:-moz-input-placeholder { /*Firefox 4~18*/
color: #ccc;
}
input::-ms-input-placeholder { /*ie*/
color: #ccc;
}
input::-o-input-placeholder { /*opera*/
color: #ccc;
}
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
}
input[type="number"] {
-moz-appearance: textfield;
}
input::-webkit-input-placeholder {
font-size: 12px;
color: rgba(195, 206, 222, 1) !important;
}
input::-moz-input-placeholder {
font-size: 12px;
color: rgba(195, 206, 222, 1) !important;
}
input:-moz-input-placeholder {
font-size: 12px;
color: rgba(195, 206, 222, 1) !important;
}
input::-ms-input-placeholder {
font-size: 12px;
color: rgba(195, 206, 222, 1) !important;
}
input::-o-input-placeholder {
font-size: 12px;
color: rgba(195, 206, 222, 1) !important;
}
textarea::-webkit-input-placeholder {
font-size: 12px;
color: rgba(195, 206, 222, 1) !important;
}
textarea::-moz-input-placeholder {
font-size: 12px;
color: rgba(195, 206, 222, 1) !important;
}
textarea:-moz-input-placeholder {
font-size: 12px;
color: rgba(195, 206, 222, 1) !important;
}
textarea::-ms-input-placeholder {
font-size: 12px;
color: rgba(195, 206, 222, 1) !important;
}
textarea::-o-input-placeholder {
font-size: 12px;
color: rgba(195, 206, 222, 1) !important;
}
</style>
<input type="number" oninput="if(value > 1) value = value.splice(0, 4)" />
<input type="range" value="0" min="0" max="100" onchange="getValue()" />
<script type="text/javascript">
</script>
<textarea>
定义多行的文本输入控件。
<button>
:定义按钮。
<select>
:定义选择列表(下拉列表)。
<select name="platform">
<option value="" selected disabled>Please select an option</option>
<option value="website">FED website</option>
<option value="amazon">Amazon</option>
<option value="walmart">Walmart</option>
<option value="other">Other</option>
<option value="not">Not purchased yet</option>
</select>
<select name="brand">
<option value="" selected disabled>Brand</option>
<option value="fed">FED</option>
<option value="flybird">Flybird</option>
<option value="yosuda">YOSUDA</option>
</select>
<script type="text/javascript">
const brandEl = document.querySelector(".module-contact-us-customer select[name='brand']");
const value = brandEl.value;
// Platform与Brand(必填)联动
const platformEl = document.querySelector(".module-contact-us-customer select[name='platform']");
const brandEl = document.querySelector(".module-contact-us-customer select[name='brand']");
platformEl.addEventListener('change', (event) => {
console.log("平台: ", event.target.value);
brandEl.value = "";
});
brandEl.addEventListener('change', (event) => {
console.log("品牌: ", event.target.value);
inputBlurTracker(brandEl, event.target.value);
});
// 动态筛选显示某些Option
function filterOptions(filterText) {
const options = selectElement.options;
for (let i = 0; i < options.length; i++) {
const option = options[i];
// 如果option文本包含筛选文本则显示,否则隐藏
option.hidden = !option.text.includes(filterText);
}
}
// 添加新的Option
// 获取select元素
const selectElement = document.getElementById('mySelect');
// 创建新的option元素
const newOption = document.createElement('option');
newOption.value = 'value1';
newOption.text = '显示文本1';
// 添加到select中
selectElement.add(newOption);
// 或者使用更简洁的方式
selectElement.add(new Option('显示文本2', 'value2'));
// 删除Option
// 通过索引删除
selectElement.remove(0); // 删除第一个option
// 通过value删除
const options = selectElement.options;
for (let i = 0; i < options.length; i++) {
if (options[i].value === 'value1') {
selectElement.remove(i);
break;
}
}
// 修改Option
// 修改第一个option的文本和值
selectElement.options[0].text = '新文本';
selectElement.options[0].value = '新值';
// 清空所有Option
selectElement.innerHTML = ''; // 方法1
selectElement.length = 0; // 方法2
// 根据数据动态生成Option列表
const data = [
{ id: 1, name: '选项1' },
{ id: 2, name: '选项2' },
{ id: 3, name: '选项3' }
];
function populateSelect(data) {
selectElement.innerHTML = ''; // 清空现有选项
data.forEach(item => {
const option = new Option(item.name, item.id);
selectElement.add(option);
});
}
populateSelect(data);
// 禁用/启用Option
// 禁用第一个option
selectElement.options[0].disabled = true;
// 启用第一个option
selectElement.options[0].disabled = false;
</script>
<optgroup>
:定义选择列表中相关选项的组合。
<option>
:定义选择列表中的选项。
<label>
:定义 input 元素的标注。
<fieldset>
:定义围绕表单中元素的边框。
<fieldset>
<legend>Personalia:</legend>
Name: <input type="text"><br>
Email: <input type="text"><br>
Date of birth: <input type="text">
</fieldset>
<legend>
:定义 fieldset 元素的标题。
<datalist>
:规定了 input 元素可能的选项列表。
<output>
:定义一个计算的结果。
标题段落标签
<h1> to <h6>
:定义 HTML 标题<p>
:定义一个段落<hr>
:定义水平线。
其他标签
<details>
标签
<details>
元素的内容对用户是不可见的,除非设置了 open 属性。浏览器兼容性差,尤其是IE与Firefox都不支持,但是可以考虑在移动端使用该标签。
<style type="text/css">
details {
list-style: none;
}
details[open] {}
details:not([open]) {}
</style>
<details ontoggle="toggleDetails()">
<summary>Copyright 1999-2011.</summary>
<p> - by Refsnes Data. All Rights Reserved.</p>
<p>All content and graphics on this web site are the property of the company Refsnes Data.</p>
</details>
<script type="text/javascript">
// 当 <details> 元素在打开或关闭时执行 JavaScript
function toggleDetails() {
//
}
</script>
postMessage
对于两个不同页面的脚本,只有当执行它们的页面位于具有相同的协议(通常为 https),端口号(443 为 https 的默认值),以及主机 (两个页面的模数 Document.domain设置为相同的值) 时,这两个脚本才能相互通信。
window.postMessage:https://developer.mozilla.org/zh-CN/docs/Web/API/Window/postMessage
MessageEvent:https://developer.mozilla.org/zh-CN/docs/Web/API/MessageEvent
Worker.postMessage:https://developer.mozilla.org/zh-CN/docs/Web/API/Worker/postMessage
BroadcastChannel.postMessage:https://developer.mozilla.org/zh-CN/docs/Web/API/BroadcastChannel/postMessage
重要问题: postMessage在什么环境会被限制通信?
Element.postMessage(
{
type: 'erp-bi-data',
data: {}
},
'*'
)
// 什么环境下无法接收到消息
window.addEventListener('message', function (e) {
console.info(e.data)
}, false)
APP调用H5方法
H5中声明函数,APP直接调用即可。
语法结构
// otherWindow:其他窗口的一个引用,比如iframe的contentWindow属性、执行window.open返回的窗口对象、或者是命名过或数值索引的window.frames。
otherWindow.postMessage(
message, // 将要发送到其他 window的数据。它将会被结构化克隆算法序列化。这意味着你可以不受什么限制的将数据对象安全的传送给目标窗口而无需自己序列化。
targetOrigin, // 通过窗口的origin属性来指定哪些窗口能接收到消息事件,其值可以是字符串"*"(表示无限制)或者一个URI。
[transfer] // 是一串和message 同时传递的 Transferable 对象. 这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权。
);
<iframe>
标签
规定一个内联框架。
- 脚本访问框架内容必须遵守[同源策略],并且无法访问非同源的 window 对象的几乎所有属性。
- 同源策略同样适用于子窗体访问父窗体的 window 对象。跨域通信可以通过 window.postMessage 来实现。
<iframe
src="https://perf.ysunlight.com/"
frameborder="0"
sandbox="allow-forms allow-same-origin allow-top-navigation allow-scripts"
></iframe>
<script type="text/javascript">
const iframe = document.querySelector('iframe');
iframe.addEventListener('load', (e) => {
console.log('加载完成', e);
}, false);
iframe.addEventListener('error', (e) => {
console.log(e, 'error事件');
});
window.onmessage = function(e) {
console.log('接收通信内容:', e.data);
}
</script>
sandbox值
- "" 启用所有限制条件
- allow-same-origin 允许将内容作为普通来源对待。如果未使用该关键字,嵌入的内容将被视为一个独立的源。
- allow-top-navigation 嵌入的页面的上下文可以导航(加载)内容到顶级的浏览上下文环境(browsing context)。如果未使用该关键字,这个操作将不可用。
- allow-forms 允许表单提交。
- allow-scripts 允许脚本执行。
iframe内嵌页面 | iframe-child.html
// 通过 window.parent 引用父窗口对象
console.log(window.parent);
// 向iframe父级页面通信
window.onload = function(e) {
console.log('载入完成:', e);
window.parent.postMessage({type: 'iframe-message', data: { name: 'Eshen', id: Date.now() }});
}
// JS操作 | 注意:同源
document.querySelectorAll('iframe')[0].contentDocument
document.querySelectorAll('iframe')[0].contentWindow
document.querySelectorAll('iframe')[0].contentDocument.querySelectorAll('iframe')
let iframeWindow = iframe.contentWindow
let iframeDocument = iframe.contentDocument
- 获取子页面数据
// 子级页面
window.parent.postMessage({
type: '',
data: {}
}, '*')
// 父级页面
window.addEventListener('message', function(e) {
// e.data
})
- 获取父页面数据(同域)
// 父级页面
window.parentFunctionPostData = function() {
//
}
// 子级页面
window.parent.parentFunctionPostData();
跨域通信
- 父页面:parent.html
<iframe id="iframeId" class="com-frame" :src="iframeLink" frameborder="0"></iframe>
<script>
cosnt iframeLink = "url";
const iframeId = document.getElementById('iframeId');
iframeId.onload = function () {
// 向BI传输数据
iframeId.contentWindow.postMessage(
{
type: 'erp-bi-data',
data: {
...userInfo,
token: getToken() ?? '',
session_id: getSessionId() ?? '',
perm: result.data.data
}
},
'*'
);
}
iframeId.addEventListener('error', (e: any) => {
initLoading.value = false;
initError.value = true;
ElMessage.error('BI图表数据加载出现异常!');
});
</script>
- 子页面: child.html
<script>
window.addEventListener('message', function (e) {
const { type, data } = e.data || {}
if (type === 'erp-bi-data') {
console.info('接收ERP数据 e.data: ', e.data)
console.info('perm字符串: ', data.perm)
window.DEFINE_TOKEN = data.perm
window.sessionStorage.setItem("biToken", data.perm)
}
}, false);
</script>
数据存储
localStorage
localStorage,即本地存储,只读的localStorage 属性允许你访问一个Document 源(origin)的对象 Storage。
相关特征
- localStorage 中的键值对总是以字符串的形式存储。
时效性
- 存储在 localStorage 的数据可以长期保留。
大小限制
- 限制5M左右,LocalStorage限制的是长度: Firefox\Chrome\Opera都是允许每个域的最大长度为5MB,但是这次IE比较大方,允许的最大长度是10MB。
基础语法
localStorage.setItem(key,value); # 设置
localStorage.getItem(key); # 获取
localStorage.removeItem(key); # 删除
localStorage.clear(); # 清空
localStorage.key(index); # 索引
storage事件
在同一个页面内发生的改变不会起作用,在同源域名的其他页面才会生效。
window.addEventListener('storage', function(e) {
console.log(e.key);
console.log(e.oldValue);
console.log(e.newValue);
console.log(e.url);
console.log(e.storageArea);
});
Store.js
Store.js是一个适用于所有用例的跨浏览器存储JS库。
代码示例
// Store current user
store.set('user', { name:'Marcus' })
// Get current user
store.get('user')
// Remove current user
store.remove('user')
// Clear all keys
store.clearAll()
// Loop over all stored values
store.each(function(value, key) {
console.log(key, '==', value)
})
配额与现额
- 最大存储配额
- 当前存储大小
JSON.stringify(localStorage).length
sessionStorage
sessionStorage,即会话存储,只读的sessionStorage属性允许你访问一个对应当前源的 session Storage 对象。
- 相关特征
- 页面会话在浏览器打开期间一直保持,并且重新加载或恢复页面仍会保持原来的页面会话。
- 在新标签或窗口打开一个页面时会复制顶级浏览会话的上下文作为新会话的上下文。
- 打开多个相同的 URL 的 Tabs 页面,会创建各自的 sessionStorage。
- 时效性
- 当页面被关闭时,存储在 sessionStorage 的数据会被清除。
- 大小限制
- 限制5M左右
- 基础语法
- 设置:sessionStorage.setItem(key,value);
- 获取:sessionStorage.getItem(key);
- 删除:sessionStorage.removeItem(key);
- 清空:sessionStorage.clear();
- 索引: sessionStorage.key(index);
配额与现额
- 最大存储配额
- 当前存储大小
JSON.stringify(sessionStorage).length
Cookie
Cookie是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。
严重警告
Cookie 随每个 HTTP 请求一起发送,因此只能存储少量数据,数据一多就会显著增加每个 Web 请求的大小,因此会增加数据传输的压力,在一些场景中会存在性能延迟问题。
浏览器对cookie个数限制
- IE: 原先为20个,后来升级为50个
- Firefox: 50个 Opera:30个
- Chrome: 180个
- Safari: 无限制
浏览器对cookie大小限制
- Firefox和Safari:4079字节
- Opera: 4096字节
- IE: 4095字节
主要用途
- 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息)
- 个性化设置(如用户自定义设置、主题等)
- 浏览器行为跟踪(如跟踪分析用户行为等)
生命周期
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT;
限制访问 Cookie
有两种方法可以确保 Cookie 被安全发送,并且不会被意外的参与者或脚本访问:Secure 属性和HttpOnly 属性。
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly
作用域
Domain 和 Path 标识定义了 Cookie 的作用域:即允许 Cookie 应该发送给哪些 URL。
配额与现额
- 最大存储配额
- 当前存储大小
IndexedDB
IndexedDB 是一种底层 API,用于在客户端存储大量的结构化数据(也包括文件/二进制大型对象(blobs))。
重要通知
IndexedDB API虽然强大,但是较为复杂,推荐利用第三方库localForage、dexie.js、PouchDB、idb、idb-keyval、JsStore、lovefield等,这些库使 IndexedDB 对开发者来说更加友好。
https://developer.mozilla.org/zh-CN/docs/Web/API/IndexedDB_API
相关特征
- 该 API 使用索引实现对数据的高性能搜索,使用 IndexedDB 执行的操作是异步执行的,以免阻塞应用程序。
- 此特性在 Web Worker 中可用。
- IndexedDB 是一个事务型数据库系统,类似于基于 SQL 的 RDBMS。
- IndexedDB 遵守同源策略。
配额与现额
- 最大存储配额
- 当前存储大小
HTML DOM
DOM(Document Object Model),即文档对象模型,DOM 模型用一个逻辑树来表示一个文档,树的每个分支的终点都是一个节点 (node),每个节点都包含着对象 (objects)。
- DOM:https://developer.mozilla.org/zh-CN/docs/Web/API/Document_Object_Model
- Element:https://developer.mozilla.org/zh-CN/docs/Web/API/Element
- HTML DOM事件:https://www.runoob.com/jsref/dom-obj-event.html
Document 对象
当浏览器载入 HTML 文档, 它就会成为 Document 对象,Document 对象提供了访问 HTML 文档中所有元素的属性与方法。
方法列表
----------------------------------------------------------------------------------
属性/方法 描述
----------------------------------------------------------------------------------
document.documentElement 返回文档的根节点
document.createElement() 创建元素节点。
document.createElementNS(namespaceURI, qualifiedName[, options]) 创建一个具有指定的命名空间 URI 和限定名称的元素。
document.createTextNode() 创建文本节点。
document.documentElement 返回文档的根节点,例如html元素
document.documentMode 返回用于通过浏览器渲染文档的模式
----------------------------------------------------------------------------------
有效的命名空间 URI
HTML - 参阅 http://www.w3.org/1999/xhtml
SVG - 参阅 http://www.w3.org/2000/svg
XBL - 参阅 http://www.mozilla.org/xbl
XUL - 参阅 http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul
document.readyState:返回当前文档的状态
----------------------------------------------------------------------------------
属性 说明
----------------------------------------------------------------------------------
uninitialized 还未开始载入
loading 载入中
interactive 已加载,文档与用户可以开始交互
complete 载入完成
----------------------------------------------------------------------------------
HTML元素对象
元素对象代表着一个 HTML 元素。
HTML属性对象
属性对象代表一个 HTML 属性。
事件对象
事件允许Javascript在HTML文档元素中注册不同事件处理程序。
Console对象
Console 对象提供了访问浏览器调试模式的信息到控制台。
console.info
console.info("输出日志");
console.log
console.log("输出日志");
// 占位符 - 字符串
console.log("%d年%d月%d日",2011,3,26);
// 占位符 - CSS样式
console.log("%c类型", "color:#f00;font-size:18px;");
console.warn
console.warn("输出日志");
console.error
console.error("输出日志");
CSSStyleDeclaration对象
CSSStyleDeclaration 对象表示一个 CSS 属性-值(property-value)对的集合。
HTMLCollection 类数组对象
HTMLCollection 对象类似一个包含 HTML 元素的数组列表,是 HTML 元素的集合。HTML DOM 中的 HTMLCollection 是即时更新的(live);当其所包含的文档结构发生改变时,它会自动更新。因此,最好是创建副本(例如,使用 Array.from)后再迭代这个数组以添加、移动或删除 DOM 节点。
const scripts = document.getElementsByTagName('script');
Array.from(scripts)
const indicatorIndexEls = document.querySelectorAll(
".define-carousel-indicator-item"
);
const indicatorIndexElsList = Array.from(indicatorIndexEls);
const button = document.getElementById('button');
button.addEventListener('click', function(e) {
console.log(e);
const { clientX, clientY, pageX, pageY, x, y, layerX, layerY, offsetX, offsetY, screenX, screenY } = e;
const { clientWidth, clientHeight, clientTop, clientLeft, offsetWidth, offsetHeight, offsetTop, offsetLeft } = e.target;
console.log(clientX, clientY, x, y, layerX, layerY, offsetX, offsetY );
console.log(clientWidth, clientHeight, clientTop, clientLeft, offsetWidth, offsetHeight, offsetTop, offsetLeft);
}, false);
window.addEventListener('click', function(e) {
console.log(e);
const { clientX, clientY, pageX, pageY, x, y, layerX, layerY, offsetX, offsetY, screenX, screenY } = e;
const { clientWidth, clientHeight, clientTop, clientLeft, offsetWidth, offsetHeight, offsetTop, offsetLeft } = e.target;
console.log(clientX, clientY, x, y, layerX, layerY, offsetX, offsetY );
}, false);
Fragment片段
- 因为文档片段存在于内存中,并不在DOM树中,所以将子元素插入到文档片段时不会引起页面回流(对元素位置和几何上的计算)。
- 因此,使用文档片段通常会带来更好的性能。
- var fragment = document.createDocumentFragment();
案例
<body>
<ul id="ul"></ul>
</body>
<script type='module'>
var element = document.getElementById('ul'); // assuming ul exists
var fragment = document.createDocumentFragment();
var browsers = ['Firefox', 'Chrome', 'Opera', 'Safari', 'Internet Explorer'];
browsers.forEach(function(browser) {
var li = document.createElement('li');
li.textContent = browser;
fragment.appendChild(li);
});
element.appendChild(fragment);
</script>
DOM创建优化
HTMLElement.prototype.appendHTML = function(html) {
var divTemp = document.createElement("div");
var nodes = null;
//文档片段,一次性append,提高性能
var fragment = document.createDocumentFragment();
divTemp.innerHTML = html;
nodes = divTemp.childNodes;
for (var i = 0; i < nodes.length; i++) {
fragment.appendChild(nodes[i].cloneNode(true));
}
this.appendChild(fragment);
nodes = null;
fragment = null;
};
var node = window.document.body;
node.appendHTML("<div></div>");
document.createDocumentFragment() 属性与方法
- children
- firstElementChild
- lastElementChild
- childElementCount
- getElementById
- prepend
- append
- querySelector
- querySelectorAll
- ELEMENT_NODE
- ATTRIBUTE_NODE
- TEXT_NODE
- CDATA_SECTION_NODE
- ENTITY_REFERENCE_NODE
- ENTITY_NODE
- PROCESSING_INSTRUCTION_NODE
- COMMENT_NODE
- DOCUMENT_NODE
- DOCUMENT_TYPE_NODE
- DOCUMENT_FRAGMENT_NODE
- NOTATION_NODE
- DOCUMENT_POSITION_DISCONNECTED
- DOCUMENT_POSITION_PRECEDING
- DOCUMENT_POSITION_FOLLOWING
- DOCUMENT_POSITION_CONTAINS
- DOCUMENT_POSITION_CONTAINED_BY
- DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC
- nodeType
- nodeName
- baseURI
- isConnected
- ownerDocument
- parentNode
- parentElement
- childNodes
- firstChild
- lastChild
- previousSibling
- nextSibling
- nodeValue
- textContent
- hasChildNodes
- getRootNode
- normalize
- compareDocumentPosition
- contains
- lookupPrefix
- lookupNamespaceURI
- isDefaultNamespace
- insertBefore
- appendChild
- replaceChild
- removeChild
- addEventListener
- removeEventListener
- dispatchEvent
特别重要
- cloneNode:克隆元素
- isEqualNode:判断两个元素是否相等
- isSameNode:判断两个元素是否相等,已废弃
// 指示器事件
function indicatorEvent() {
const carouselIndicatorEl = document.querySelector(
".define-carousel-indicator"
);
carouselIndicatorEl.addEventListener("click", indicatorIndexModel, false);
}
function indicatorIndexModel(e) {
const El = e.target;
const isIndexEl = hasClass(El, "define-carousel-indicator-item");
if (!isIndexEl) return;
const indicatorIndexEls = document.querySelectorAll(
".define-carousel-indicator-item"
);
const indicatorIndexElsList = Array.from(indicatorIndexEls);
for (let i = 0; i < indicatorIndexElsList.length; i++) {
const indexEl = indicatorIndexElsList[i];
console.info(indexEl.isEqualNode(El));
}
console.info(El, indicatorIndexElsList);
DOM对象
- Anchor:该对象代表HTML文档中的超链接;
- Document:该对象代表当前浏览器窗口中载入的HTML文档,该对象是Window对象的一部分,可以通过window.document属性对其访问;
- Body:该对象代表HTML文档中的主体,文档中的所有的内容都应该位于该对象中;
- Button:该对象代表一个按钮;
- Event:该对象代表HTML中的事件,例如单击、获得输入焦点等;
- Form:该对象代表HTML表单;
- Frame:该对象代表一个HTML框架;
- Image:该对象代表HTML文档中嵌入的图像元素;
- Input:该对象代表表单对象中的表单域,包括Checkbox、File、Hiddle、Passowrd、Radio、Reset、Submit和Text;
- Table:该对象代表一个HTML表格;
- Window:该对象属于浏览器对象,代表一个浏览器窗口;
- Location:该对象属于浏览器对象,含有当前文档的URL信息,可以通过window.location属性来访问;
- History:该对象属于浏览器对象,存储当前窗口的浏览历史记录,可以通过window.history属性来访问;
DOM 根节点
----------------------------------------------------------------------------------
属性 描述 案例
----------------------------------------------------------------------------------
document.documentElement 全部文档 document.documentElement.fontSize = '24px'; 即html {font-size: 24px;}
document.body 文档的主体
----------------------------------------------------------------------------------
DOM节点类型
(元素节点、文本节点)
----------------------------------------------------------------------------------
类型 nodeType值 说明 nodeName节点名称(元素标签名,例如LI、BODY、DIV等)
----------------------------------------------------------------------------------
Element 1 元素
Attr 2 属性
Text 3 文本
Comment 8 注释
Document 9 文档
DocumentFragment 11 文档片段
----------------------------------------------------------------------------------
console.log(document.body.nodeType); // 1
console.log(document.nodeType); // 9
var fragment = document.createDocumentFragment();
console.log(fragment.nodeType); // 11
DOM查询
const ID = document.getElementById("elem");
var tag = document.getElementsByTagName("div");
var cls = document.getElementsByClassName("elem"); //在 IE 5,6,7,8 中无效
document.querySelector("id || class || tag"); // 同类第一个元素
document.querySelectorAll("id || class || tag"); // 返回数组, 同类所有元素
document.querySelector('[id^="GalleryStatus"]');
- 统计HTML文档中包含多少个Element标签?
document.getElementsByTagName("*").length
Array.from(document.getElementsByTagName("*")).sort()
DOM遍历
- 上一个兄弟元素:Element.previousSibling
- 下一个兄弟元素:Element.nextSibling
- 子元素:Element.children
<div><p>1</p><p>2</p></div>
<script type="text/javascript">
var tag = document.getElementsByTagName("div");
console.log(tag.children); // HTMLCollection(2) [p, p]
</script>
parentNode与parentElement混淆点
- 父级节点:Element.parentNode,父级结点,包括document。
- 父级元素:Element.parentElement,父级元素,不包括document。
- 对象的第一个子元素:Element.firstChild
- 对象的最后一个子元素:Element.lastChild
- 对象的所有子节点:Element.childNodes; || 返回数组(包含元素节点、文本节点) 备注:previousSibling、nextSibling、firstChild、lastChild、childNodes等,HTML写法不同,值不同
<div><p>1</p><p>2</p></div> //Element.firstChild会是<p>1</p>
<!--而这样写, 值会是#text, 因为JS不会忽略换行等差异-->
<body data-url="https://test.ysunlight.com">
<div id="container">
<div></div>
<div id='list'>
<p>1</p>
<p>2</p>
<p>3</p>
</div>
<div></div>
</div>
<div class="container"></div>
<header></header>
</body>
const list = document.getElementById("list");
console.log(list.previousSibling); //#text
console.log(list.nextSibling); //#text
console.log(list.children); //HTMLCollection(3) [p, p, p]
console.log(list.firstChild); //#text
console.log(list.lastChild); //#text
console.log(list.childNodes); //NodeList(7) [text, p, text, p, text, p, text]
DOM元素
// 创建结点
document.createElement("elem");
// 创建文本
document.createTextNode("text");
// 追加(对象内部结尾)内容(元素、文本)
Element.appendChild(elem);
Element.appendChild(text);
Element.insertBefore(newNode,childNode):在已有节点的子节点前插入一个新的子节点,newNode,childNode必填,否则会报错
<div id="contain">
<div></div>
<div id="china"></div>
<div></div>
</div>
<script type="text/javascript">
const contain = document.getElementById("contain");
const china = document.getElementById("china");
var newNode = document.createElement('p');
contain.insertBefore(newNode, china);
</script>
<div id="contain">
<div></div>
<p></p><div id="china"></div>
<div></div>
</div>
替换元素 || 内容:Element.replaceChild(newNode,childNode); 移除子元素:parentElement.removeChild(elem); || 备注:DOM 需要清楚您需要删除的元素,以及它的父元素
删除元素:Element.remove(); 克隆元素:Element.cloneNode(boolean):创建节点的拷贝,并返回该副本; //true: 克隆节点及其属性,以及后代; false: 仅克隆节点及其后代;
<div id="contain">
<p></p>
</div>
<script type="text/javascript">
const contain = document.getElementById("contain");
contain.cloneNode(true);
</script>
DOM属性
// 创建属性
var attr = Object.createAttribute("attr");
// 获取属性
Object.getAttribute("attr");
// 设置属性
Object.setAttribute("attr", "value");
// 移除属性
Object.removeAttribute("attr");
// 判断属性
Object.hasAttribute("bar")
DOM内容
// 当前元素(自身及内部)HTML代码
Object.outerHTML = "HTML代码"; // 设置
var htmlCtn = Object.outerHTML; // 获取
// 当前元素(内部)HTML代码
Object.innerHTML = "HTML代码"; // 设置
var htmlCtn = Object.innerHTML; // 获取
// 当前元素(内部)文本 || Firefox不支持InnerText属性
Object.innerText = "文本";
var txt = Object.innerText;
// Firefox不支持InnerText属性,故使用textContent属性来代替(Internet Explorer 8 以及更早的版本不支持此属性)
if (document.all) {
Object.innerText = "文本";
} else {
Object.textContent = "文本";
}
DOM CSS
https://www.runoob.com/jsref/prop-element-classlist.html
属性:classList,存在兼容性
<div id="Test"></div>
<div id="Test" class="name sex"></div>
<script type="text/javascript">
const Test = document.getElementById("Test");
console.log(Test.classList); //DOMTokenList [value: ""]
console.log(Test.classList); //DOMTokenList(2) ["name", "sex", value: "name sex"]
//添加类值
Test.classList.add('height');
//判断类是否包含该类值
Element.classList.contains(); //返回布尔值,判断指定的类名是否存在
Element.classList.item(); //返回元素中索引值对应的类名。索引值从 0 开始
Element.classList.remove(); //移除元素中一个或多个类名
Element.classList.toggle(); //在元素中切换类名
</script>
属性:className
<div id="Test"></div>
<script type="text/javascript">
const Test = document.getElementById("Test");
console.log(Test.className);
</script>
设置样式
- 设置一个:Object.style.height = "100px";
- 设置多个:Object.style.cssText = "font-size:16px;color:red";
- 获取内嵌CSS样式:var sty = Object.style.height;
- IE获取内联CSS样式:var sty = Object.style.height;
- 非IE获取内联CSS样式
var obj = document.getElementById("test");
var cpdStyle = document.defaultView.getComputedStyle(obj, null); // getComputedStyle ie9以上
console.log(cpdStyle.height);
window.getComputedStyle(obj, null);
Window.getComputedStyle()
- getComputedStyle: https://www.runoob.com/w3cnote/window-getcomputedstyle-method.html
- getComputedStyle: https://developer.mozilla.org/zh-CN/docs/Web/API/Window/getComputedStyle
CSSStyleDeclaration
CSSStyleDeclaration 对象表示一个 CSS 属性-值(property-value)对的集合。
属性
----------------------------------------------------------------------------------
属性 描述
----------------------------------------------------------------------------------
cssText 设置或返回样式声明文本,cssText 对应的是 HTML 元素的 style 属性。
length 返回样式中包含多少条声明。
parentRule 返回包含当前规则的规则。
----------------------------------------------------------------------------------
方法
----------------------------------------------------------------------------------
属性 描述
----------------------------------------------------------------------------------
getPropertyPriority(key) 返回指定的 CSS 属性是否设置了 "important!" 属性。
getPropertyValue(key) 返回指定的 CSS 属性值。
item(i) 通过索引方式返回 CSS 声明中的 CSS 属性名。
removeProperty(key) 移除 CSS 声明中的 CSS 属性。
setProperty(key, value) 在 CSS 声明块中新建或者修改 CSS 属性。
----------------------------------------------------------------------------------
- 代码示例
<div style="color: red; font-size: 50px;"></div>
<script type="text/javascript">
const el = document.getElementsByTagName("div")[0];
for (i = 0; i < el.style.length; i++) {
console.info(el.style.item(i)); // color, font-size
}
</script>
DOM监听机制
- MutationObserver: https://developer.mozilla.org/zh-CN/docs/Web/API/MutationObserver
旧有 Mutation 事件列表【已被MutationObserver替代】
DOMAttrModified(节点属性变更) DOMAttributeNameChanged(节点属性属性节点名字改变) DOMCharacterDataModified(节点中的文本节点改变 DOMElementNameChanged(节点移除) DOMNodeInserted(节点子节点插入) DOMNodeRemoved(节点子节点移除) DOMNodeInsertedIntoDocument(节点插入文档) DOMSubtreeModified(节点子节点修改)
var textNode = document.createTextNode("My text");
textNode.addEventListener ('DOMNodeInserted', () => {
console.log('Insert');
}, false);
var container = document.getElementById("container");
container.appendChild (textNode);
var select = document.getElementById("numbers");
select.addEventListener ('DOMNodeRemoved', () => {
console.log('移除');
}, false);
select.remove();
Intersection Observer
IntersectionObserver: https://developer.chrome.com/blog/intersectionobserver/
var intersectionObserver = new IntersectionObserver(function(entries) {
// If intersectionRatio is 0, the target is out of view
// and we do not need to do anything.
if (entries[0].intersectionRatio <= 0) return;
loadItems(10);
console.log('Loaded new items');
});
// start observing
intersectionObserver.observe(document.querySelector('.scrollerFooter'));
MutationObserver
MutationObserver接口提供了监视对DOM树所做更改的能力。它被设计为旧的Mutation Events功能的替代品,该功能是DOM3 Events规范的一部分。 构造函数:MutationObserver() 创建并返回一个新的 MutationObserver 它会在指定的DOM发生变化时被调用。 方法disconnect():阻止 MutationObserver 实例继续接收的通知,直到再次调用其observe()方法,该观察者对象包含的回调函数都不会再被调用。 方法observe():配置MutationObserver在DOM更改匹配给定选项时,通过其回调函数开始接收通知。 方法takeRecords():从MutationObserver的通知队列中删除所有待处理的通知,并将它们返回到MutationRecord对象的新Array中。
代码示例
// 配置观察选项
const config = {
childList: true, // 观察子节点的变化
subtree: true, // 观察整个子树
attributes: true, // 观察节点属性的变化
characterData: true // 观察节点内容或文本的变化
};
// 当观察到变动时执行的回调函数
const callback = function(mutationsList, observer) {
// Use traditional 'for loops' for IE 11
for (let mutation of mutationsList) {
if (mutation.type === 'childList') {
console.log('A child node has been added or removed.');
}
else if (mutation.type === 'attributes') {
console.log('The ' + mutation.attributeName + ' attribute was modified.');
}
}
};
// 创建一个观察器实例并传入回调函数
const observer = new MutationObserver(callback);
// 以上述配置开始观察目标节点
const targetNode = document.getElementById('some-id');
observer.observe(targetNode, config);
// 之后,可停止观察
observer.disconnect();
new ResizeObserver
ResizeObserver 接口监视 Element 内容盒或边框盒或者 SVGElement 边界尺寸的变化。
https://developer.mozilla.org/zh-CN/docs/Web/API/ResizeObserver
entries对象数据结构
// new ResizeObserver((entries) => {});
[
{
borderBoxSize: [
{ inlineSize: 1029, blockSize: 50 }
],
contentBoxSize: [
{ inlineSize: 1029, blockSize: 50 }
],
contentRect: {
"x": 0,
"y": 0,
"width": 1029,
"height": 50,
"top": 0,
"right": 1029,
"bottom": 50,
"left": 0
},
devicePixelContentBoxSize: {inlineSize: 1029, blockSize: 50},
target: Element
}
]
<div id="observer">DOM</div>
<script type="text/javascript">
const resizeObserver = new ResizeObserver((entries) => {
console.info("entries: ", entries);
for (const entry of entries) {
//
}
});
const observerEl = document.querySelector("#observer");
// 注册,会执行一次
resizeObserver.observe(observerEl);
window.setTimeout(() => {
// 再次执行一次
observerEl.style = "height:50px;";
}, 3000);
window.setTimeout(() => {
// 解绑
resizeObserver.unobserve(observerEl);
}, 10000);
</script>
HTML DOM对象原型 TS
BeforeUnloadEvent
DOMStringMap
ErrorEvent
GlobalEventHandlers
HTMLAnchorElement
HTMLAreaElement
HTMLAudioElement
HTMLBRElement
HTMLBaseElement
HTMLBaseFontElement
HTMLBodyElement
HTMLButtonElement
HTMLCanvasElement
HTMLContentElement
HTMLDListElement
HTMLDataElement
HTMLDataListElement
HTMLDialogElement
HTMLDivElement
HTMLDocument
HTMLEmbedElement
HTMLFieldSetElement
HTMLFormControlsCollection
HTMLFormElement
HTMLFrameSetElement
HTMLHRElement
HTMLHeadElement
HTMLHeadingElement
HTMLHtmlElement
HTMLIFrameElement
HTMLImageElement
HTMLInputElement
HTMLIsIndexElement
HTMLKeygenElement
HTMLLIElement
HTMLLabelElement
HTMLLegendElement
HTMLLinkElement
HTMLMapElement
HTMLMediaElement
HTMLMetaElement
HTMLMeterElement
HTMLModElement
HTMLOListElement
HTMLObjectElement
HTMLOptGroupElement
HTMLOptionElement
HTMLOptionsCollection
HTMLOutputElement
HTMLParagraphElement
HTMLParamElement
HTMLPictureElement
HTMLPreElement
HTMLProgressElement
HTMLQuoteElement
HTMLScriptElement
HTMLSelectElement
HTMLShadowElement
HTMLSourceElement
HTMLSpanElement
HTMLStyleElement
HTMLTableCaptionElement
HTMLTableCellElement
HTMLTableColElement
HTMLTableDataCellElement
HTMLTableElement
HTMLTableHeaderCellElement
HTMLTableRowElement
HTMLTableSectionElement
HTMLTemplateElement
HTMLTextAreaElement
HTMLTimeElement
HTMLTitleElement
HTMLTrackElement
HTMLUListElement
HTMLUnknownElement
HTMLVideoElement
HashChangeEvent
History
ImageData
Location
MessageChannel
MessageEvent
MessagePort
Navigator
NavigatorGeolocation
NavigatorID
NavigatorLanguage
NavigatorOnLine
NavigatorPlugins
PageTransitionEvent
Plugin
PluginArray
PopStateEvent
PortCollection
PromiseRejectionEvent
RadioNodeList
Transferable
ValidityState
Window
WindowBase64
WindowEventHandlers
WindowTimers
CSS
classList属性: ie9及其以下版本不支持
classList.length classList.value classList.item classList.contains classList.add classList.remove classList.toggle classList.replace classList.supports classList.toString classList.entries classList.forEach classList.keys classList.values
/* istanbul ignore next */
import Vue from 'vue';
const isServer = Vue.prototype.$isServer;
const SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g;
const MOZ_HACK_REGEXP = /^moz([A-Z])/;
const ieVersion = isServer ? 0 : Number(document.documentMode);
/* istanbul ignore next */
const trim = function(string) {
return (string || '').replace(/^[\s\uFEFF]+|[\s\uFEFF]+$/g, '');
};
/* istanbul ignore next */
const camelCase = function(name) {
return name.replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) {
return offset ? letter.toUpperCase() : letter;
}).replace(MOZ_HACK_REGEXP, 'Moz$1');
};
/* istanbul ignore next */
export const on = (function() {
if (!isServer && document.addEventListener) {
return function(element, event, handler) {
if (element && event && handler) {
element.addEventListener(event, handler, false);
}
};
} else {
return function(element, event, handler) {
if (element && event && handler) {
element.attachEvent('on' + event, handler);
}
};
}
})();
/* istanbul ignore next */
export const off = (function() {
if (!isServer && document.removeEventListener) {
return function(element, event, handler) {
if (element && event) {
element.removeEventListener(event, handler, false);
}
};
} else {
return function(element, event, handler) {
if (element && event) {
element.detachEvent('on' + event, handler);
}
};
}
})();
/* istanbul ignore next */
export const once = function(el, event, fn) {
var listener = function() {
if (fn) {
fn.apply(this, arguments);
}
off(el, event, listener);
};
on(el, event, listener);
};
/* istanbul ignore next */
export function hasClass(el, cls) {
if (!el || !cls) return false;
if (cls.indexOf(' ') !== -1) throw new Error('className should not contain space.');
if (el.classList) {
return el.classList.contains(cls);
} else {
return (' ' + el.className + ' ').indexOf(' ' + cls + ' ') > -1;
}
};
/* istanbul ignore next */
export function addClass(el, cls) {
if (!el) return;
var curClass = el.className;
var classes = (cls || '').split(' ');
for (var i = 0, j = classes.length; i < j; i++) {
var clsName = classes[i];
if (!clsName) continue;
if (el.classList) {
el.classList.add(clsName);
} else {
if (!hasClass(el, clsName)) {
curClass += ' ' + clsName;
}
}
}
if (!el.classList) {
el.className = curClass;
}
};
/* istanbul ignore next */
export function removeClass(el, cls) {
if (!el || !cls) return;
var classes = cls.split(' ');
var curClass = ' ' + el.className + ' ';
for (var i = 0, j = classes.length; i < j; i++) {
var clsName = classes[i];
if (!clsName) continue;
if (el.classList) {
el.classList.remove(clsName);
} else {
if (hasClass(el, clsName)) {
curClass = curClass.replace(' ' + clsName + ' ', ' ');
}
}
}
if (!el.classList) {
el.className = trim(curClass);
}
};
/* istanbul ignore next */
export const getStyle = ieVersion < 9 ? function(element, styleName) {
if (isServer) return;
if (!element || !styleName) return null;
styleName = camelCase(styleName);
if (styleName === 'float') {
styleName = 'styleFloat';
}
try {
switch (styleName) {
case 'opacity':
try {
return element.filters.item('alpha').opacity / 100;
} catch (e) {
return 1.0;
}
default:
return (element.style[styleName] || element.currentStyle ? element.currentStyle[styleName] : null);
}
} catch (e) {
return element.style[styleName];
}
} : function(element, styleName) {
if (isServer) return;
if (!element || !styleName) return null;
styleName = camelCase(styleName);
if (styleName === 'float') {
styleName = 'cssFloat';
}
try {
var computed = document.defaultView.getComputedStyle(element, '');
return element.style[styleName] || computed ? computed[styleName] : null;
} catch (e) {
return element.style[styleName];
}
};
/* istanbul ignore next */
export function setStyle(element, styleName, value) {
if (!element || !styleName) return;
if (typeof styleName === 'object') {
for (var prop in styleName) {
if (styleName.hasOwnProperty(prop)) {
setStyle(element, prop, styleName[prop]);
}
}
} else {
styleName = camelCase(styleName);
if (styleName === 'opacity' && ieVersion < 9) {
element.style.filter = isNaN(value) ? '' : 'alpha(opacity=' + value * 100 + ')';
} else {
element.style[styleName] = value;
}
}
};
/* istanbul ignore next */
const trim = function(string) {
return (string || '').replace(/^[\s\uFEFF]+|[\s\uFEFF]+$/g, '');
};
function hasClass(el, cls) {
if (!el || !cls) return false;
if (cls.indexOf(' ') !== -1) {
throw new Error('className should not contain space.');
}
//el.classList兼容性:[Chrome-8.0, IE-10.0, Firefox-3.6, Safari-5.1, Opera-11.5]
if (el.classList) {
return el.classList.contains(cls);
}
if (el.className == '') {
return false;
}
return el.className.indexOf(cls) != -1;
}
/* istanbul ignore next */
function addClass(el, cls) {
if (!el) return;
var curClass = el.className;
var classes = (cls || '').split(' ');
for (var i = 0, j = classes.length; i < j; i++) {
var clsName = classes[i];
if (!clsName) continue;
if (el.classList) {
el.classList.add(clsName);
} else {
if (!hasClass(el, clsName)) {
curClass += ' ' + clsName;
}
}
}
if (!el.classList) {
el.className = curClass;
}
};
/* istanbul ignore next */
function removeClass(el, cls) {
if (!el || !cls) return;
var classes = cls.split(' ');
var curClass = ' ' + el.className + ' ';
for (var i = 0, j = classes.length; i < j; i++) {
var clsName = classes[i];
if (!clsName) continue;
if (el.classList) {
el.classList.remove(clsName);
} else {
if (hasClass(el, clsName)) {
curClass = curClass.replace(' ' + clsName + ' ', ' ');
}
}
}
if (!el.classList) {
el.className = trim(curClass);
}
};
function toggleClass(el, cls) {
if (!el || !cls) return;
if (hasClass(el, cls)) {
removeClass(el, cls);
return;
}
addClass(el, cls);
}
/**
* @param {*} el 元素
* @param {*} cls 类名
*
*/
function toggleClass(el, cls) {
if (!el) {
throw new Error('el should not be null.');
}
var name = el.className;
el.className.match(cls) ?
name = name.replace(cls, '') :
name = name + ' ' + cls;
name = name.replace(/(^\s*)|(\s*$)/g, '');
el.className = name;
}
var el = document.getElementById('elem');
toggleClass(el, 'hello');
/**
* 添加类名
*
*/
function addClass(el, cls) {
var name = el.className;
el.className.match(cls) ?
name = el.className :
name += ' ' + cls;
//移除左右空格
name = name.replace(/(^\s*)|(\s*$)/g, '');
el.className = name;
}
Additional
DOMActivate 在元素变为活动状态时发生。
DOMAttrModified 当属性被添加、移除或属性值被脚本修改时触发。
DOMCharacterDataModified 当脚本更改TextNode的值时触发。
DOMFocusIn 在元素获得焦点之前发生。
DOMFocusOut 在元素失去焦点之前发生。
DOMMouseScroll 当鼠标滚轮滚动时发生。
DOMNodeInserted 将节点添加到元素时发生在节点上。
DOMNodeInsertedIntoDocument 当节点插入文档时发生在节点上。
DOMNodeRemoved 当节点从其父节点中移除时发生在节点上。
DOMNodeRemovedFromDocument 从文档中删除节点时发生在节点上。
DOMSubtreeModified 当在属于它的子树中发生修改时在节点上触发。
dragdrop 将拖动的数据放置在可能的目标元素上时发生。
dragexit 当用户在拖动操作期间将鼠标指针移出元素时发生在元素上。
draggesture 当用户开始拖动操作时在源元素上发生。
overflow 当元素的内容或大小发生更改并导致出现滚动条时发生。
overflowchanged 当元素的内容或大小发生更改并导致滚动条出现或消失时发生。
underflow 当元素的内容或大小发生更改并导致滚动条消失时发生。
HTML DOM 位置与尺寸
window对象尺寸属性
// 电脑分辨率: 3456 * 2160 | 网页尺寸: 1366 * 2028
const {
outerWidth, // 表示整个浏览器窗口的宽度,包括侧边栏、窗口镶边和调正窗口大小的边框。
outerHeight, // 表示整个浏览器窗口的高度,包括侧边栏、窗口镶边和调正窗口大小的边框。
innerWidth, // 返回以像素为单位的窗口的内部宽度(包括滚动条)
innerHeight, // 返回以像素为单位的窗口的内部高度(包括滚动条)
pageXOffset, // window.scrollX 的别名
pageYOffset, // window.scrollY 的别名
scrollX, // 返回文档/页面水平方向滚动的像素值。
scrollY, // 返回文档在垂直方向已滚动的像素值。
screenLeft, // Window.screenX 的别名
screenTop, // Window.screenY 的别名
screenX, // 返回浏览器左边框到左边屏幕边缘的 CSS 像素数。
screenY, // 返回从用户浏览器的上边界到屏幕最顶端的CSS 像素。
} = window;
screen对象尺寸属性
const {
width, // 显示屏/屏幕的整体宽度
height, // 显示屏/屏幕的整体高度
availWidth, // 返回浏览器窗口可占用的水平宽度(单位:像素)。
availHeight, // 返回浏览器窗口在屏幕上可占用的垂直空间,即最大高度(注意: 不包括底部任务栏)。
availLeft, //
availTop, //
colorDepth, // 返回屏幕的颜色深度(color depth)。根据 CSSOM( CSS 对象模型 ) 视图,为兼容起见,该值总为 24。
pixelDepth, // 返回屏幕的位深度/色彩深度(bit depth)。根据 CSSOM( CSS 对象模型 ) 视图,为兼容起见,该值总为 24。
} = window.screen;
Document.body对象尺寸属性
Document.body 表示当前文档中的
<body>
或<frameset>
元素,如果不存在此类元素,则为 null。。
// 有三种方法能够确定浏览器窗口的尺寸
对于Internet Explorer、Chrome、Firefox、Opera 以及 Safari
window.innerHeight - 浏览器窗口的内部高度(包括滚动条)
window.innerWidth - 浏览器窗口的内部宽度(包括滚动条)
对于 Internet Explorer 8、7、6、5
document.documentElement.clientHeight
document.documentElement.clientWidth
其他
document.body.clientHeight
document.body.clientWidth
// 综合方案
let width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
let height = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
网页可见区域宽: document.body.clientWidth
网页可见区域高: document.body.clientHeight
网页可见区域宽: document.body.offsetWidth (包括边线的宽)
网页可见区域高: document.body.offsetHeight (包括边线的高)
网页正文全文宽: document.body.scrollWidth
网页正文全文高: document.body.scrollHeight
网页被卷去的高: document.body.scrollTop
网页被卷去的左: document.body.scrollLeft
网页正文部分上: window.screenTop
网页正文部分左: window.screenLeft
屏幕物理分辨率的高: window.screen.height
屏幕物理分辨率的宽: window.screen.width
屏幕可用工作区高度: window.screen.availHeight
屏幕可用工作区宽度: window.screen.availWidth
屏幕缩放因子:window.devicePixelRatio
屏幕逻辑分辨率:window.screen.width * window.devicePixelRatio (缩放因子与物理分辨率的乘积)
页面的实际大小
document.documentElement.scrollWidth document.documentElement.scrollHeight
Element对象尺寸属性
const {
clientWidth, // 元素内部的宽度,包括内边距(padding),但不包括边框(border)、外边距(margin)和垂直滚动条(如果存在)。
clientHeight, // 元素内部的高度,包括内边距(padding),但不包括边框(border)、外边距(margin)和垂直滚动条(如果存在)。
clientLeft, //
clientTop, //
scrollWidth, //
scrollHeight, //
scrollLeft, //
scrollTop, //
scrollLeftMax, //
scrollTopMax, //
} = Element;
HTMLElement对象尺寸属性
const {
offsetWidth, // 返回一个元素的布局宽度,包含元素的边框 (border)、水平线上的内边距 (padding)、竖直方向滚动条 (scrollbar)(如果存在的话)、以及 CSS 设置的宽度 (width) 的值。
offsetHeight,
offsetLeft,
offsetTop, // 返回当前元素相对于其 offsetParent 元素的顶部内边距的距离。
} = HTMLElement;
offsetParent
返回一个指向最近的(指包含层级上的最近)包含该元素的定位元素或者最近的 table, td, th, body 元素。当元素的 style.display 设置为 "none" 时,offsetParent 返回 null。offsetParent 很有用,因为 offsetTop 和 offsetLeft 都是相对于其内边距边界的。
Event对象尺寸属性
Event: https://developer.mozilla.org/zh-CN/docs/Web/API/Event
鼠标事件: https://developer.mozilla.org/zh-CN/docs/Web/API/MouseEvent
const {
clientX, //
clientY, //
layerX, //
layerY, //
offsetX, //
offsetY, //
pageX, //
pageY, //
screenX, //
screenY, //
x, //
y //
} = e;
EventTarget对象尺寸属性
const {
clientWidth, //
clientHeight, //
clientLeft, //
clientTop, //
offsetWidth, //
offsetHeight, //
offsetLeft, //
offsetTop, //
} = e.target;
获取相对文档坐标
obj.onmousemove = function(e) {
var e = e || window.event;
var x = e.x; //获取文档横坐标
var y = e.y; //获取文档纵坐标
}
获取相对元素边坐标
obj.onmousemove = function(e) {
var e = e || window.event;
var x = e.offsetX; //获取当前元素焦点横坐标(相对于元素)
var y = e.offsetY; //获取当前元素纵坐标
}
获取文档距离
获取DOM元素距离文档顶部位置
getElDocTop: function(el) {
if (!el || el.nodeType != 1) {
console.log("请传入元素对象Element");
return ;
}
if(el.parentElement) {
return this.getElDocument(el.parentElement) + el.offsetTop;
}
return el.offsetTop;
},
DOM盒子尺寸各种属性
client • layer • offset • page • screen
offsetParent:https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLElement/offsetParent
方法
----------------------------------------------------------------------------------
函数名 接口说明
----------------------------------------------------------------------------------
position 获取当前元素的绝对位置,返回类似{“left” : 10, “top” : 10}的对象
getAbsoluteLeft 返回当前元素与页面左端的绝对距离,以象素为单位
getAbsoluteTop 返回当前元素与页面顶端的绝对距离,以象素为单位
clientWidth 获取元素的clientWidth值,即可视区域大小,包括content + border + margin,返回的结果等于getOffsetWidth减去垂直滚动条的宽度,同getClientWidth 接口
clientHeight 获取元素的clientHeight值,即可视区域大小,包括content + border + margin,返回的结果等于getOffsetHeight减去水平滚动条的宽度,同getClientHeight 接口
scrollWidth 返回元素内容的整体宽度,以象素为单位。如果元素有滚动条,则这一宽度值包含元素的不可见部分,同getScrollWidth 接口
scrollHeight 返回元素内容的整体高度,以象素为单位。如果元素有滚动条,则这一宽度值包含元素的不可见部分,同getScrollHeight 接口和setScrollLeft接口
scrollTop 读取或设置元素节点的垂直滚动条的位置。可用于在元素可见区域的坐标和元素整体坐标之间进行纵坐标换算,getScrollTop和setScrollTop接口
obj.offsetLeft //元素相对于父元素的left
obj.offsetTop //元素相对于父元素的top
obj.offsetWidth //元素的宽度(width+padding+border),同getOffsetHeight 接口
obj.offsetHeight //元素的高度
getBoundingClientRect() [对象类型]返回一个矩形对象,包含bottom、left、top、right、width、height、x、y(IE只返回top,lef,right,bottom四个值,需要重新兼容处理),返回 一个TextRectangle对象,即使DOM里没有文本也能返回TextRectangle对象.
getClientRects() [数组类型]获取元素占据页面的所有矩形区域,包含bottom、left、top、right、width、height、x、y ,返回一个TextRectangle集合,就是TextRectangleList对象
offsetParent 当某个元素的父元素进行了CSS定位时(absolute或者relative),则这个元素的offsetParent属性的取值为其父元素
----------------------------------------------------------------------------------
e对象尺寸属性
document.addEventListener('click', function(e) {
//
}, false);
//
const button = document.getElementById('button');
button.addEventListener('click', function(e) {
console.log(e);
}, false);
// e 对象
{
// 鼠标相对于浏览器视口左上角的偏移
clientX // 事件对象的焦点坐标X(基于窗体对象,受滚动条影响)
clientY // 事件对象的焦点坐标Y(基于窗体对象,受滚动条影响,向下滚动时,值变大)
// 触点相对于HTML文档左边沿的的坐标 (和用户滚动位置无关,不受滚动条影响)
pageX //(基于文档内容, 不受滚动条影响)
pageY //(基于文档内容, 不受滚动条影响)
x // 与e.clientX相似
y // 与e.clientY相似
offsetX // 基于事件对象
offsetY // 基于事件对象
layerX // 基于offsetParent元素
layerY // 基于offsetParent元素
// 基于屏幕,包括浏览器的tab栏、URL输入框、标签栏 | 注意,如果把浏览器拖放到屏幕非可视区域,则e.screenX值会小于e.clientX等值
// [注意] 在扩展多显示器的时候,该值可能是多个显示器的屏幕叠加值
screenX: 254, // 当前元素边框border距离屏幕(显示器明亮部分)的left值
screenY: 471, // 与screenX, 鼠标相对于显示器屏幕左上角的偏移
}
e.target 目标对象
{
clientWidth: Number, // 当前元素:content + padding
clientHeight: Number, // 当前元素:content + padding
clientTop: Number, // 当前元素border-top-width
clientLeft: Number, // 当前元素border-left-width
// 当前元素相对于该对象偏移(offset)返回一个指向最近的(指包含层级上的最近)包含该元素的定位元素或者最近的 table,td,th,body元素。当元素的 style.display 设置为 "none" 时,offsetParent 返回 null。
// 该元素或其祖先元素的 style.display 为 "none", 或者该元素的 style.position 被设为 "fixed",则该属性返回 null。
offsetParent: HTMLElement | null // body.offsetParent === null
// offsetWidth是测量包含元素的边框(border)、水平线上的内边距(padding)、竖直方向滚动条(scrollbar)(如果存在的话)、以及CSS设置的宽度(width)的值。
// 这个属性将会 round(四舍五入)为一个整数。
offsetWidth: 60, // 当前元素: content + padding + border
offsetHeight: 50, // 当前元素: content + padding + border
// 返回当前元素相对于其 offsetParent 元素的顶部内边距的距离。
// 当前元素style.position = 'absolute' | 'relative':offsetTop = Element.offsetParent.paddingTop + Element.top + Element.marginTop
offsetTop: 275, // 当前元素的border-top到html元素的padding-top之间的高度,不包括border-top与padding-top,因此html的padding与border设置后会影响offsetTop的值,因为被挤压占位了(整体高度已经固定document.)。
// 返回当前元素左上角相对于 HTMLElement.offsetParent 节点的左边界偏移的像素值。
// 当前元素style.position = 'absolute' | 'relative':offsetLeft = Element.offsetParent.paddingLeft + Element.Left + Element.marginLeft
offsetLeft: 170,
scrollWidth // 当前元素: content
scrollHeight // 当前元素: content
scrollTop //
scrollLeft //
}
offsetParent offsetTop offsetLeft offsetWidth offsetHeight
内高度、内宽度: 内边距 + 内容框
clientWidth clientHeight 2、外高度,外宽度: 边框 + 内边距 + 内容框 offsetWidth offsetHeight 3、上边框、左边框 clientTop clientLeft 5、上边偏移量,左边的偏移量 offsetTop offsetLest
获取元素样式兼容语法
el.style仅仅能够获取元素style设置的内置样式 注意:如果不设置元素的宽度和高度,那么在非IE浏览器下返回默认的宽度和高度。在IE下面返回auto字符串
function getStyle(nodeName, keyName) {
// nodeName:"#id"、".class"、"div"、document.querySelector(target)
if (!nodeName || !((nodeName).nodeType === 1 || typeof nodeName === 'string')) throw new Error('缺失节点对象');
if (typeof nodeName === 'string' && !document.querySelector(nodeName)) throw new Error('缺失节点对象');
var nodeTarget = (nodeName).nodeType === 1 ? nodeName : document.querySelector(nodeName);
// 判断浏览器是否为IE-IE8及IE8以下:!!window.ActiveXObject || "ActiveXObject" in window
return (!!window.ActiveXObject || "ActiveXObject" in window) ? nodeName.currentStyle[keyName] : document.defaultView.getComputedStyle(nodeName, null)[keyName];
}
getStyle('#app', 'height');
<link>
和<style type="text/css">
标签写入的样式
获取总结:以上的三种CSS获取元素大小的方法,只能获取元素的CSS大小,却无法获取元素本身实际的大小。比如加上了内边距、滚动条、边框之类的。
var obj = document.styleSheets[0]; // [object StyleSheetList] 样式表的个数<link>var rule = null;// [object CSSRule]
// 非IE [object CSSRuleList]
// IE [object CSSRuleList]
rule = obj.cssRules ? obj.cssRules[0] : obj.rules[0];
alert(rule.style.width);
cssRules(或rules)只能获取到内联和链接样式的宽和高,不能获取到行内和计算后的样式。
clientWidth和clientHeight
这组属性可以获取元素可视区的大小,可以得到元素内容及内边距所占据的空间大小。返回了元素大小,但没有单位,默认单位是px,如果你强行设置了单位,比如100em之类,它还是会返回px的大小。(CSS获取的话,是照着你设置的样式获取)。对于元素的实际大小,clientWidth和clientHeight理解方式如下: a. 增加边框,无变化; b. 增加外边距,无变化; c. 增加滚动条,最终值等于原本大小减去滚动条的大小; d. 增加内边距,最终值等于原本大小加上内边距的大小;
<style type="text/css">
#test{
background-color: green;
width: 200px;
height: 200px;
border: solid 5px red; /* 对应a理解,结果:200,200 */
margin: 10px; /* 对应b理解,结果:200,200*/
padding: 20px; /* 对应c理解,结果:240,240*/
overflow: scroll; /* 对应d理解,结果:223,223,223=200(css大小)+40(两边内边距)-17(滚动条宽度)*/
}
</style>
<div id="test"></div>
<script type="text/javascript">
window.onload = function(){
var obj = document.getElementById("test");
alert(obj.clientWidth + "," + obj.clientHeight);
};
</script>
注意:如果说没有设置任何CSS的宽和高度,那么非IE浏览器会算上滚动条和内边距的计算后的大小,而IE浏览器则返回0(IE8已修复)。
scrollWidth和scrollHeight
这组属性可以获取滚动内容(可见内容)的元素大小。返回了元素大小,默认单位是px。如果没有设置任何CSS的宽和高度,它会得到计算后的宽度和高度。对于元素的实际大小,scrollWidth和scrollHeight理解如下:
1. 增加边框,不同浏览器有不同解释(下面在IE8中运行正常,IE6运行不正常):
a) Firefox和Opera浏览器会增加边框的大小,220x220 b) IE、Chrome和Safari浏览器会忽略边框大小,200x200 c) IE浏览器只显示它本来内容的高度,200x18(IE8已经修改该问题) 2. 增加内边距,最终值会等于原本大小加上内边距大小,220x220,IE为220x38 3. 增加滚动条,最终值会等于原本大小减去滚动条大小,184x184,IE为184x18 4. 增加外边据,无变化。 5. 增加内容溢出,Firefox、Chrome和IE获取实际内容高度,Opera比前三个浏览器获取的高度偏小,Safari比前三个浏览器获取的高度偏大。
offsetWidth和offsetHeight
这组属性可以返回元素实际大小,包含边框、内边距和滚动条。返回了元素大小,默认单位是px。如果没有设置任何CSS的宽和高度,他会得到计算后的宽度和高度。对于元素的实际大小,offsetWidth和offsetHeight理解如下:
1.增加边框,最终值会等于原本大小加上边框大小,为220;
2.增加内边距,最终值会等于原本大小加上内边距大小,为220;
3.增加外边据,无变化;
4.增加滚动条,无变化,不会减小;
对于元素大小的获取,一般是块级(block)元素并且以设置了CSS大小的元素较为方便。如果是内联元素(inline)或者没有设置大小的元素就尤为麻烦,所以,建议使用的时候注意。
<style type="text/css">
#test{
background-color: green;
width: 200px;
height: 200px;
border: solid 10px red; /*结果:220,220*/
margin: 10px; /*结果:220,220(无变化)*/
padding: 10px; /*结果:240,240*/
overflow:scroll; /*结果:240,240(无变化)*/
}
</style>
<div id="test">test div element</div>
<script type="text/javascript">
window.onload = function(){
var obj = document.getElementById("test");
alert(obj.offsetWidth + "," + obj.offsetHeight);
};
</script>
clientLeft和clientTop获取边框大小
这组属性可以获取元素设置了左边框和上边框的大小。目前只提供了Left和Top这组,并没有提供Right和Bottom。如果四条边宽度不同的话,可以直接通过计算后的样式获取,或者采用以上三组获取元素大小的减法求得。 右边框的宽度:obj.offsetWidth-obj.clientWidth-obj.clientLeft 底边框的宽度:obj.offsetHeight-obj.clientHeight-obj.clientTop
<style type="text/css">
#test{
background-color: green;
width: 200px;
height: 200px;
border-top: solid 10px red;s
border-right: solid 20px #00ff00;
border-bottom: solid 30px blue;
border-left: solid 40px #808080;
}
</style>
<div id="test">test div element</div>
<script type="text/javascript">
window.onload = function(){
var obj = document.getElementById("test");
alert(obj.clientLeft + "," + obj.clientTop); // 40,10
};
</script>
scrollTop和scrollLeft
这组属性可以获取滚动条被隐藏(滚动条上方区域)的区域大小,也可设置定位到该区域。如果要让滚动条滚动到最初始的位置,那么可以写一个函数:
const scrollX = (((t = document.documentElement) || (t = document.body.parentNode)) && typeof t.scrollLeft == 'number' ? t : document.body).scrollLeft,
scrollY = (((t = document.documentElement) || (t = document.body.parentNode)) && typeof t.scrollTop == 'number' ? t : document.body).scrollTop;
function scrollStart (element) {
if ( element.scrollTop != 0 ) {
element.scrollTop = 0;
}
}
getClientRects()
返回一个指向客户端中每一个盒子的边界矩形的矩形集合。 每个ClientRect对象包含一组描述该边框的只读属性——left、top、right和bottom,单位为像素,这些属性值是相对于视口的top-left的
getBoundingClientRect()
除了 width 和 height 以外的属性是相对于视图窗口的左上角来计算的。 当前元素设置style.display = 'none';时,该对象的属性值基本都为0 注意:IE、Firefox3+、Opera9.5、Chrome、Safari支持,在IE中,默认坐标从(2,2)开始计算,导致最终距离比其他浏览器多出两个像素,我们需要做个兼容。
const el = document.body;
console.log(el.getBoundingClientRect());
// 包括属性如下
el.getBoundingClientRect() = {
bottom: 768 // 元素下边距离页面上边的距离
height: 768
left: 0 // 元素左边距离页面左边的距离
right: 1366 // 元素右边距离页面左边的距离
top: 0 // 元素上边距离页面上边的距离
width: 1366
x: 0
y: 0
}
// 兼容IE
document.documentElement.clientTop; //非IE为0,IE为2
document.documentElement.clientLeft; //非IE为0,IE为2
functiongGetRect (element) {
var rect = element.getBoundingClientRect();
var top = document.documentElement.clientTop;
var left= document.documentElement.clientLeft;
return{
top: rect.top - top,
bottom: rect.bottom - top,
left: rect.left - left,
right: rect.right - left
}
}
CSS DOM
CSS 对象模型是一组允许用 JavaScript 操纵 CSS 的 API。它很像 DOM,但针对的是 CSS 而不是 HTML。它允许用户动态地读取和修改 CSS 样式。
HTML BOM
BOM (Browser Object Model),即浏览器对象模型,将一个浏览器的各个组成部分封装成对象(Browser)供调用使用。
Window 对象
Window 对象表示浏览器中打开的窗口。
属性
----------------------------------------------------------------------------------
属性 描述
----------------------------------------------------------------------------------
closed 返回窗口是否已被关闭。
defaultStatus 设置或返回窗口状态栏中的默认文本。
document 对 Document 对象的只读引用。(请参阅对象)
frames 返回窗口中所有命名的框架。该集合是 Window 对象的数组,每个 Window 对象在窗口中含有一个框架。
history 对 History 对象的只读引用。请参数 History 对象。
screen 对 Screen 对象的只读引用。请参数 Screen 对象。
localStorage 在浏览器中存储 key/value 对。没有过期时间。
length 设置或返回窗口中的框架数量。
location 用于窗口或框架的 Location 对象。请参阅 Location 对象。
name 设置或返回窗口的名称。
navigator 对 Navigator 对象的只读引用。请参数 Navigator 对象。
opener 返回对创建此窗口的窗口的引用。
parent 返回父窗口。
sessionStorage 在浏览器中存储 key/value 对。 在关闭窗口或标签页之后将会删除这些数据。
self 返回对当前窗口的引用。等价于 Window 属性。
status 设置窗口状态栏的文本。
top 返回最顶层的父窗口。
----------------------------------------------------------------------------------
方法
----------------------------------------------------------------------------------
方法 描述
----------------------------------------------------------------------------------
alert() 显示带有一段消息和一个确认按钮的警告框。
atob() 解码一个 base-64 编码的字符串,是ascii to binary,用于将ascii码解析成binary数据,即Base64的解码过程。
btoa() 创建一个 base-64 编码的字符串,是binary to ascii,用于将binary的数据用ascii码表示,即Base64的编码过程
blur() 把键盘焦点从顶层窗口移开。
clearInterval() 取消由 setInterval() 设置的 timeout。
clearTimeout() 取消由 setTimeout() 方法设置的 timeout。
close() 关闭浏览器窗口。
confirm() 显示带有一段消息以及确认按钮和取消按钮的对话框。
createPopup() 创建一个 pop-up 窗口。
focus() 把键盘焦点给予一个窗口。
getSelection() 返回一个 Selection 对象,表示用户选择的文本范围或光标的当前位置。
getComputedStyle() 获取指定元素的 CSS 样式。
matchMedia() 该方法用来检查 media query 语句,它返回一个 MediaQueryList对象。
moveBy() 可相对窗口的当前坐标把它移动指定的像素。
moveTo() 把窗口的左上角移动到一个指定的坐标。
open() 打开一个新的浏览器窗口或查找一个已命名的窗口。
print() 打印当前窗口的内容。
prompt() 显示可提示用户输入的对话框。
resizeBy() 按照指定的像素调整窗口的大小。
resizeTo() 把窗口的大小调整到指定的宽度和高度。
scroll() 已废弃。 该方法已经使用了 scrollTo() 方法来替代。
scrollBy() 按照指定的像素值来滚动内容。
scrollTo() 把内容滚动到指定的坐标。
setInterval() 按照指定的周期(以毫秒计)来调用函数或计算表达式。
setTimeout() 在指定的毫秒数后调用函数或计算表达式。
stop() 停止页面载入。
postMessage() 安全地实现跨源通信。
----------------------------------------------------------------------------------
代码示例
// window.matchMedia("(max-width: 600px)")
{
matches: false,
media: "(max-width: 600px)",
onchange: null
}
// 判断屏幕(screen/viewport)窗口大小
if (window.matchMedia("(max-width: 700px)").matches) {
/* 窗口小于或等于 700 像素 */
} else {
/*窗口大于 700 像素 */
}
// 判断屏幕(screen/viewport)窗口大小,在小于等于 700 像素时修改背景颜色为黄色,大于 700 像素时修改背景颜色为粉红色
function myFunction(x) {
if (x.matches) { // 媒体查询
document.body.style.backgroundColor = "yellow";
} else {
document.body.style.backgroundColor = "pink";
}
}
var x = window.matchMedia("(max-width: 700px)")
myFunction(x) // 执行时调用的监听函数
x.addListener(myFunction) // 状态改变时添加监听器
Navigator 对象
Navigator 对象包含有关浏览器的信息。
----------------------------------------------------------------------------------
属性 说明
----------------------------------------------------------------------------------
appCodeName 返回浏览器的代码名
appName 返回浏览器的名称
appVersion 返回浏览器的平台和版本信息
cookieEnabled 返回指明浏览器中是否启用 cookie 的布尔值
platform 返回运行浏览器的操作系统平台
userAgent 返回由客户机发送服务器的user-agent 头部的值
geolocation 返回浏览器的地理位置信息
language 返回浏览器使用的语言
onLine 返回浏览器是否在线,在线返回 ture,否则返回 false
product 返回浏览器使用的引擎(产品)
----------------------------------------------------------------------------------
第三方库
- platform.js
https://github.com/bestiejs/platform.js
<script src="https://cdnjs.cloudflare.com/ajax/libs/platform/1.3.6/platform.min.js"></script>
<script>
const os = platform.os.family;
console.log('Operating System:', os);
</script>
代码示例
var ua = navigator.userAgent.toLowerCase();
var isIE = userAgent.indexOf("compatible") > -1 && userAgent.indexOf("MSIE") > -1; // 判断是否IE<11浏览器
var isEdge = userAgent.indexOf("Edge") > -1 && !isIE; // 判断是否IE的Edge浏览器
var isIE11 = userAgent.indexOf('Trident') > -1 && userAgent.indexOf("rv:11.0") > -1;
if ( /mobile/i.test(navigator.userAgent) ) {
// 移动端
}
// 判断IE
// Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.2 Safari/605.1.15
const ua = navigator.userAgent.toLowerCase();
let Brower = {
ua: ua,
ios: ua.match(/iphone/i) != null,
android: ua.match(/android/i) != null,
huawei: ua.match(/\/huawei/g) != null, //华为
samsung: ua.match(/\Wsm-/g) != null, //三星
qihu: ua.match(/build\/mmb29m/g) != null, //360
chrome: ua.match(/chrome/i) != null,
safari: ua.match(/iphone/i) != null && ua.match(/safari\/[1-9]/i) != null,
uc: ua.match(/ucbrowser/i) != null,
qq: ua.match(/QQ\/[1-9]/i) != null,
fb: ua.match(/fb.{2,}\//) != null,
weibo: ua.match(/weibo/g) != null,
fbixm: (ua.match(/iphone/) != null && ua.match(/fb.{2,}\//) != null && window.screen.height == 812 && window.screen.width == 375) && (window.screen.height - document.documentElement.clientHeight) < 120,
wxix: ua.match(/iphone/) != null && ua.match(/micromessenger/) != null && window.screen.height == 812 && window.screen.width == 375,
wxixb: (window.screen.height - document.documentElement.clientHeight) > 120,
tab: window.screen.height - document.documentElement.clientHeight > 10,
wxixm: (ua.match(/iphone/) != null && ua.match(/micromessenger/) != null && window.screen.height == 812 && window.screen.width == 375) && (window.screen.height - document.documentElement.clientHeight) < 120,
iphoneX: () => {
return (ua.match(/iphone/) != null && ((!iBer.$var.isMiniProgram && window.screen.height >= 812) || (iBer.$var.isMiniProgram && window.screen.height >= 724))) && (window.screen.height - document.documentElement.clientHeight) < 120
},
wx: ua.match(/micromessenger/) != null, // 微信
system: {
port: window.location.port,
env: window.location.port == 8080,
docH: window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
}
}
检测移动端与非移动端
// 移动端、非移动端
const isMobileDevice = /(AppleWebKit.*Mobile.*)|android|avantgo|blackberry|bada\/|bb|meego|iphone|ipad|ipod|iemobile|kindle|midp|mmp|mobile|nokia|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|symbian|smartphone|s(ony)?ericsson|tizen|windows (phone|ce)|webos|xda|xiino/i.test(window.navigator.userAgent);
var inBrowser = typeof window !== 'undefined';
var inWeex = typeof WXEnvironment !== 'undefined' && !!WXEnvironment.platform;
var weexPlatform = inWeex && WXEnvironment.platform.toLowerCase();
var UA = inBrowser && window.navigator.userAgent.toLowerCase();
var isIE = UA && /msie|trident/.test(UA);
var isIE9 = UA && UA.indexOf('msie 9.0') > 0;
var isEdge = UA && UA.indexOf('edge/') > 0;
var isAndroid = (UA && UA.indexOf('android') > 0) || (weexPlatform === 'android');
var isIOS = (UA && /iphone|ipad|ipod|ios/.test(UA)) || (weexPlatform === 'ios');
var isChrome = UA && /chrome\/\d+/.test(UA) && !isEdge;
var isPhantomJS = UA && /phantomjs/.test(UA);
var isFF = UA && UA.match(/firefox\/(\d+)/);
判断不同平台
function getPlatformType() {
const userAgent = window.navigator.userAgent || window.navigator.vendor || window.opera;
// Android
if (/android/i.test(userAgent)) {
return 1;
}
// IOS,不是微软流媒体设备
else if (/iPad|iPhone|iPod/i.test(userAgent) && !window.MSStream) {
return 2;
}
// Windows Phone
else if (/windows phone/i.test(userAgent)) {
return 3;
}
// Windows
else if (/win(dows )?nt/i.test(userAgent)) {
return 4;
}
// HarmonyOS
else if (/HarmonyOS|HMSCore/i.test(userAgent)) {
return 5;
}
// macOS
else if (/macintosh|mac os x/i.test(userAgent)) {
return 6;
}
// Linux
else if (/linux/i.test(userAgent)) {
return 7;
}
// Chrome OS
else if (/cros/i.test(userAgent)) {
return 8;
}
// BlackBerry
else if (/blackberry|bb10|rim/i.test(userAgent)) {
return 9;
}
// 其他移动设备
else if (/Mobile|Tablet|Phone|BlackBerry|IEMobile|Opera Mini|webOS|Fennec|Silk/i.test(userAgent)) {
return 10;
}
// 其他设备
else {
return 11;
}
}
浏览器类型
function getBrowserType() {
const userAgent = window.navigator.userAgent || window.navigator.vendor || window.opera;
if (/msie|trident/i.test(userAgent)) {
const match = userAgent.match(/(?:MSIE |Trident.*rv:)(\d+)/);
return 'Internet Explorer/' + (match?.[1] || '');
}
else if (/edg/i.test(userAgent)) {
const match = userAgent.match(/Edg\/(\d+)/);
return 'Microsoft Edge/' + (match?.[1] || '');
}
else if (/chrome|crios|crmo/i.test(userAgent)) {
const match = userAgent.match(/Chrome\/(\d+)/);
return 'Chrome/' + (match?.[1] || '');
}
else if (/firefox|fxios/i.test(userAgent)) {
const match = userAgent.match(/Firefox\/(\d+)/);
return 'Firefox/' + (match?.[1] || '');
}
else if (/safari/i.test(userAgent) && !/crios|crmo|edg/i.test(userAgent)) {
const match = userAgent.match(/Version\/(\d+).+?Safari\//);
return 'Safari/' + (match?.[1] || '');
} else if (/opr|opera/i.test(userAgent)) {
const match = userAgent.match(/OPR\/(\d+)/);
return 'Opera/' + (match?.[1] || '');
} else {
return 'Unknown';
}
}
常用 User-Agent
> 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36 OPR/26.0.1656.60',
> 'Opera/8.0 (Windows NT 5.1; U; en)',
> 'Mozilla/5.0 (Windows NT 5.1; U; en; rv:1.8.1) Gecko/20061208 Firefox/2.0.0 Opera 9.50',
> 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; en) Opera 9.50',
> 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:34.0) Gecko/20100101 Firefox/34.0',
> 'Mozilla/5.0 (X11; U; Linux x86_64; zh-CN; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10',
> 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.57.2 (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2',
> 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36',
> 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11',
> 'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.133 Safari/534.16',
> 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.11 TaoBrowser/2.0 Safari/536.11',
> 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.71 Safari/537.1 LBBROWSER',
> 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; LBBROWSER)',
> 'Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.84 Safari/535.11 SE 2.X MetaSr 1.0',
> 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; SV1; QQDownload 732; .NET4.0C; .NET4.0E; SE 2.X MetaSr 1.0)',
> 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.122 UBrowser/4.0.3214.0 Safari/537.36'
# 移动端
'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1'
Screen 对象
Screen 对象包含有关客户端显示屏幕的信息。
属性与方法
----------------------------------------------------------------------------------
属性 说明
----------------------------------------------------------------------------------
screen.availWidth 返回访问者屏幕的宽度,以像素计,减去界面特性,比如窗口任务栏
screen.availHeight
screen.width: 包括任务栏
screen.height
screen.colorDepth
screen.pixelDepth
screen.availLeft
screen.availTop
screen.orientation
----------------------------------------------------------------------------------
History 对象
History 对象包含用户(在浏览器窗口中)访问过的 URL。 https://developer.mozilla.org/zh-CN/docs/Web/API/History https://developer.mozilla.org/zh-CN/docs/Web/API/WindowEventHandlers/onpopstate
属性与方法
window.history.length:返回一个Integer代表会话历史中元素数量的 ,包括当前加载的页面。 window.history.scrollRestoration:允许 Web 应用程序在历史导航中明确设置默认滚动恢复行为。 window.history.state:返回一个any表示历史堆栈顶部状态的值。{ params: {} } window.history.go(n):通过传递0或不带参数调用它来刷新当前页面 window.history.back():加载历史列表中的前一个 URL。 window.history.forward():加载历史列表中的下一个 URL。
window.history.pushState(state, title [, url]):将一个条目添加到浏览器的会话历史堆栈中。 window.history.replaceState(stateObj, title, [url]):更新历史堆栈上的最新条目以具有指定的数据、标题和 URL(如果提供)。
事件
popstate:每当处于激活状态的历史记录条目发生变化时。 调用history.back()、history.forward()、history.go()方法,以及a 标签的锚点也会触发该事件。
----------------------------------------------------------------------------------
方法 说明
----------------------------------------------------------------------------------
back() 加载 history 列表中的前一个 URL
forward() 加载 history 列表中的下一个 URL
go() 加载 history 列表中的某个具体页面
----------------------------------------------------------------------------------
Location 对象
Location 对象包含有关当前 URL 的信息。
属性与方法
window.location.replace: window.location.href: window.location.ancestorOrigins: window.location.origin: window.location.protocol: window.location.host: window.location.hostname: window.location.port: window.location.pathname: window.location.search: window.location.hash: window.location.assign: window.location.reload: window.location.toString:
----------------------------------------------------------------------------------
属性 描述
----------------------------------------------------------------------------------
hash 返回一个URL的锚部分
host 返回一个URL的主机名和端口
hostname 返回URL的主机名
href 返回完整的URL
pathname 返回的URL路径名。
port 返回一个URL服务器使用的端口号
protocol 返回一个URL协议
search 返回一个URL的查询部分
----------------------------------------------------------------------------------
----------------------------------------------------------------------------------
方法 说明
----------------------------------------------------------------------------------
assign() 载入一个新的文档
reload() 重新载入当前文档
replace() 用新的文档替换当前文档
----------------------------------------------------------------------------------
代码示例
// 创建 a 标签并设置 href 属性
var url = document.createElement('a');
url.href = 'https://www.runoob.com/?s=python#test';
console.log(url.href); // https://www.runoob.com/?s=python
console.log(url.protocol); // https:
console.log(url.host); // www.runoob.com
console.log(url.hostname); // www.runoob.com
console.log(url.port); // (输出为空 - https 默认端口为 443)
console.log(url.pathname); // /
console.log(url.search); // en-US/search
console.log(url.hash); // #test
console.log(url.origin); // https://www.runoob.com
存储对象
Web 存储 API 提供了 sessionStorage (会话存储) 和 localStorage(本地存储)两个存储对象来对网页的数据进行添加、删除、修改、查询操作。
- localStorage 用于长久保存整个网站的数据,保存的数据没有过期时间,直到手动去除。
- sessionStorage 用于临时保存同一窗口(或标签页)的数据,在关闭窗口或标签页之后将会删除这些数据。
HTML Event(事件)
- 事件参考:https://developer.mozilla.org/zh-CN/docs/Web/Events
- KeyboardEvent:https://developer.mozilla.org/zh-CN/docs/Web/API/KeyboardEvent
- HTML 事件属性:https://www.runoob.com/tags/ref-eventattributes.html
- Events in HTML:http://help.dottoro.com/lhwfcplu.php
注册事件
// 事件规范
document.addEventListener(type, listener, {
capture: false, //是否为捕获
passive: true, //告诉浏览器不会设置事件默认行为,即不会设置e.preventDefault()等事件,
once: false //once 属性就是表明该监听器是一次性的,执行一次后就被自动 removeEventListener 掉
}
// 在用户离开网页时触发,例如如点击一个链接,刷新页面,提交表单,关闭浏览器等
document.addEventListener('pagehide', function() {
//
}, false)
- 事件管理
const EventUtils = {
// 视能力分别使用dom0||dom2||IE方式 来绑定事件
// 添加事件
addEvent: function(element, type, handler) {
if (element.addEventListener) {
element.addEventListener(type, handler, false);
} else if (element.attachEvent) {
element.attachEvent("on" + type, handler);
} else {
element["on" + type] = handler;
}
},
// 移除事件
removeEvent: function(element, type, handler) {
if (element.removeEventListener) {
element.removeEventListener(type, handler, false);
} else if (element.detachEvent) {
element.detachEvent("on" + type, handler);
} else {
element["on" + type] = null;
}
},
// 获取事件目标
getTarget: function(event) {
return event.target || event.srcElement;
},
// 获取 event 对象的引用,取到事件的所有信息,确保随时能使用 event
getEvent: function(event) {
return event || window.event;
},
// 阻止事件(主要是事件冒泡,因为 IE 不支持事件捕获)
stopPropagation: function(event) {
if (event.stopPropagation) {
event.stopPropagation();
} else {
event.cancelBubble = true;
}
},
// 取消事件的默认行为
preventDefault: function(event) {
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
}
};
- e.persisted
为了查看页面是直接从服务器上载入还是从缓存中读取,你可以使用 PageTransitionEvent 对象的 persisted 属性来判断。 如果页面从浏览器的缓存中读取该属性返回 ture,否则返回 false。
document.addEventListener('', function(e) {
e.persisted
}, false);
注销事件处理程序
El.onclick = null;
El.removeEventListener(eventType, eventHandle,false);
default-passive-events
- NPM:https://www.npmjs.com/package/default-passive-events
- GitHub:https://github.com/zzarcon/default-passive-events
核心
pointer-events:设置元素指针事件是否有效,即是否支持鼠标事件,在画布区操作元素很有实用价值
合成事件
创建自定义事件
const event = new Event("build");
// 监听该事件。
elem.addEventListener(
"build",
(e) => {
/* … */
},
false,
);
// 分派该事件。
elem.dispatchEvent(event);
CustomEvent
创建自定义事件,可以向事件对象添加更多数据。
// 创建自定义事件
const catFound = new CustomEvent("animalfound", {
detail: {
name: "猫",
},
bubbles: true,
composed: true
});
const dogFound = new CustomEvent("animalfound", {
detail: {
name: "狗",
},
});
// 触发事件,注意obj可以任何对象
[Element].dispatchEvent(catFound);
[Element].dispatchEvent(dogFound);
接收事件
// 添加合适的事件监听器
Element.addEventListener("animalfound", function(e) {
console.log(e.detail.name);
});
完整代码示例
const button = document.getElementsByTagName("button")?.[0];
button.addEventListener(
"click",
function () {
const event = new CustomEvent("start-rorate-turntable-event", {
data: {
name: "猫",
},
bubbles: true,
composed: true,
});
document.dispatchEvent(event);
},
false
);
// 监听外部事件
document.addEventListener(
"start-rorate-turntable-event",
function (e) {
console.info(e);
},
false
);
Hammer
Hammer 是一个开源库,可以识别由触摸、鼠标和指针事件做出的手势。它没有任何依赖项,而且很小,只有7.34 kB 压缩 + gzip 压缩! http://hammerjs.github.io/ https://github.com/hammerjs/hammer.js
丝般顺滑的触摸运动方案
https://github.com/AlloyTeam/PhyTouch
<div onclick="setCommond(event)"></div>
<script type="text/javascript">
function setCommond(e) {
console.log(e);
}
</script>
事件流
描述:从页面中接收事件的顺序。
IE事件流:事件冒泡流。
思想:事件开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档); 案例:例如单击div,则冒泡为div、body、html、document。
Netscape事件流:事件捕获流。
思想:不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收到事件; 案例:例如单击div,则冒泡为document、html、body、div。
DOM事件流的三个阶段
事件捕获阶段:首先发生事件捕获,为截获事件提供机会; 处于目标阶段:然后是实际目标接收到事件; 事件冒泡阶段:可以在这个阶段对事件作出相应。
event属性
Element.addEventListener("click", function(e) {
if (e.keyCode == 13) {
console.log("ASCII码-enter键");
}
});
- e.preventDefault():阻止事件默认行为。
- e.stopPropagation():停止冒泡。
e.target与e.currentTarget区别
e.target:获取当前对象; e.currentTarget:获取绑定事件的元素对象。
[~] e.target:获取事件源 [~] 兼容FF和其它浏览器
function persion(e) {
let e = e || window.event;
let tar = e.target || e.srcElement;
}
屏蔽默認事件
window.addEventListener('touchmove', function(e) {
e.preventDefault();
e.stopPropagation();
}, false);
事件句柄语法
[DOM Object].onclick = function() {};
[DOM Object].addEventListener(
eventType, // 声明事件类型,不含on;如:'click'、'mouseover'等
eventHandle, // 声明事件函数
userCapture // 声明是否启用捕捉,默认为false(事件冒泡);true为(事件捕获,IE不支持)
);
IE浏览器
[DOM Object].attachEvent(
eventType, // 含on;如:'onclick'、'onmouseover'等
eventHandle // 声明事件函数
);
兼容写法
function addEventListener(obj, eventType, eventHandle, userCapture) {
if (obj.attachEvent) {
obj.attachEvent('on' + eventType, eventHandle);
return ;
}
obj.addEventListener(eventType, eventHandle, userCapture);
}
DOM 规范
addEventListener(type, listener[, useCapture ])
addEventListener(type, listener[, options ])
obj.attachEvent(eventType, eventHandle)中this指向兼容BUG
<button id='getTarget'></button>
<script type="text/javascript">
var obj = document.getElementById('getTarget');
obj.attachEvent('onclick', function(e) {
var e = e || window.event;
var tar = e.srcElement || e.target;
console.log(this); //[object Window]
console.log(tar.nodeName); //BUTTON
});
</script>
事件委托、事件代理
功效:高效(无需轮询绑定子元素事件)、可扩展性(后期追加子元素同样生效) 思想:给父元素绑定事件,用来监听子元素的冒泡事件,并找到是哪个子元素的事件源。 兼容性:window.event.srcElement与event.target 实现代码
<ul id='ul'>
<li>1</li>
<li>2</li>
</ul>
<script type="text/javascript">
var obj = document.getElementById('ul');
obj.addEventListener('click', function(e) {
var e = e || window.event;
if (e.target.nodeName.toString().toLowerCase() == 'ul') {
return false;
} else {
console.log(e.target.innerHTML);
}
}, false);
</script>
鼠标事件
见pc/event.list.md
鼠标事件执行顺序
mousedown > mousemove > mouseup > click
触摸事件
见mobile/event.list.md
事件执行顺序
touchstart > touchmove > touchend > click
事件变量
touches:当前屏幕上所有触摸点的列表; targetTouches: 当前对象上所有触摸点的列表; changedTouches: 涉及当前(引发)事件的触摸点的列表。
三者区别
用一个手指接触屏幕,触发事件,此时这三个属性有相同的值; 用第二个手指接触屏幕,此时,touches有两个元素,每个手指触摸点为一个值; 当两个手指触摸相同元素时,targetTouches和touches的值相同,否则targetTouches 只有一个值; 当两个手指触摸相同元素时,changedTouches此时只有一个值,为第二个手指的触摸点; 用两个手指同时接触屏幕,此时changedTouches有两个值,每一个手指的触摸点都有一个值手指滑动时,三个值都会发生变化; 一个手指离开屏幕,touches和targetTouches中对应的元素会同时移除,而changedTouches仍然会存在元素; 手指都离开屏幕之后,touches和targetTouches中将不会再有值,changedTouches还会有一个值,此值为最后一个离开屏幕的手指的接触点。
touchstart和touchmove使用: e.targetTouches[0].pageX 或 (jquery)e.originalEvent.targetTouches[0].pageX
touchend使用: e.changedTouches[0].pageX 或 (jquery)e.originalEvent.changedTouches[0].pageX
DOMContentLoaded
当 HTML 文档完全解析,且所有延迟脚本(
<script defer src="…">
和<script type="module">
)下载和执行完毕后,会触发 DOMContentLoaded 事件。它不会等待图片、子框架和异步脚本等其他内容完成加载。
DOMContentLoaded 事件: https://developer.mozilla.org/zh-CN/docs/Web/API/Document/DOMContentLoaded_event
DOMContentLoaded 不会等待样式表加载,但延迟脚本会等待样式表,而且 DOMContentLoaded 事件排在延迟脚本之后。此外,非延迟或异步的脚本(如
<script>
)将等待已解析的样式表加载。
if (document.addEventListener) {
document.addEventListener( "DOMContentLoaded", completed, false );
//IE6、IE7
} else if (document.attachEvent) {
//确保当页面是在iframe中加载时,事件依旧会被安全触发
document.attachEvent('onreadystatechange', function() {
if (document.readyState == 'complete') {
document.detachEvent('onreadystatechange', completed);
}
});
if (document.documentElement.doScroll && typeof window.frameElement === "undefined") {
try {
document.documentElement.doScroll('left');
} catch(e) {
return setTimeout(completed, 20);
}
}
}
延迟 DOMContentLoaded
document.addEventListener("DOMContentLoaded", (event) => {
console.log("DOM 完全加载和解析");
});
for (let i = 0; i < 1_000_000_000; i++);
// 这段同步脚本将会延迟 DOM 解析,
// 所以 DOMContentLoaded 事件将会延迟执行。
load 事件
load 事件在整个页面及所有依赖资源如样式表和图片都已完成加载时触发。
window.addEventListener("load", (event) => {
console.info("load: ");
});
Drag(拖曳) 和 Drop(落下)
DOM drop属性与方法
ondrag # 拖动 ondragend #结束 ondragenter #拖动文件进入元素区域 ondragleave # 拖动文件离开元素区域 ondragover #拖动文件浮于元素上 ondragstart ondrop #拖动元素到元素OK
Drag(拖曳) 和 Drop(落下):https://www.runoob.com/html/html5-draganddrop.html
简介与核心思想
拖动对象相对于焦点区域,是以拖动对象的中心点为基准的,即拖动对象的中心点坐标是否与焦点区域相交
注意事项
Vue上绑定事件时,必须声明@dragover.prevent,否则@drop不生效
拖曳对象
const drapEle = document.querySelector('#drapEle');
// 拖曳开始
drapEle.addEventListener('dragstart', function(e) {
// 设置数据,传输给拖放区域
e.dataTransfer.setData("target_id",e.target.dataset['id']);
});
// 拖曳过程
dropEle.addEventListener('drag', function(e) {
//
});
// 拖曳结束
dropEle.addEventListener('dragend', function(e) {
//
});
落下区域
<section id='dropEle'></section>
<script type="text/javascript">
const dropEle = document.querySelector('#dropEle');
// 拖曳对象进入目标区域
dropEle.addEventListener('dragenter', function(e) {
//
});
// 拖曳对象离开目标区域
dropEle.addEventListener('dragleave', function(e) {
//
});
// 拖曳对象覆盖目标区域 | 屏蔽拖放图片落下时默认打开的缺陷问题
dropEle.addEventListener('dragover', function(e) {
e.stopPropagation();
e.preventDefault();
});
// 拖曳对象落下目标区域
dropEle.addEventListener('drop', function(e) {
//获取被拖对象设置的数据
const data = e.dataTransfer.getData("target_id");
console.log(data);
});
</script>
var dropEle = document.getElementById("uploadContent");
var Elem = document.getElementById("Element");
var Target = document.getElementById("Target");
// 被拖动对象
Elem.addEventListener('dragstart', function(e) {
//e.stopPropagation();
//e.preventDefault();
e['name'] = "dragstart";
//设置数据,传输给拖放区域
e.dataTransfer.setData("target_id",e.target.dataset['id']);
console.log(e);
});
// 被拖动对象
Elem.addEventListener('dragend', function(e) {
//e.stopPropagation();
//e.preventDefault();
e['name'] = "dragend";
console.log(e);
});
// 被拖动对象
Elem.addEventListener('drag', function(e) {
//e.stopPropagation();
//e.preventDefault();
e['name'] = "drag";
// console.log(e);
});
//拖动对象进入焦点区域
dropEle.addEventListener('dragenter', function(e) {
e.stopPropagation();
e.preventDefault();
e['name'] = "dragenter";
//console.log(e);
});
//拖动对象离开焦点区域
dropEle.addEventListener('dragleave', function(e) {
e.stopPropagation();
e.preventDefault();
e['name'] = "dragleave";
//console.log(e);
});
//拖动对象位于焦点区域, | 这个声明很重要,否则拖放图片时,会默认从当前页面打开,记住呀,记住呀
dropEle.addEventListener('dragover', function(e) {
e.stopPropagation();
e.preventDefault();
e.dataTransfer.dropEffect = 'copy';
e['name'] = "dragover";
console.log(e);
});
//拖动对象已经存储在焦点区域
dropEle.addEventListener('drop', function (e) {
e.stopPropagation();
e.preventDefault();
e['name'] = "drop";
console.log(e);
//获取被拖对象设置的数据
console.log(e.dataTransfer.getData("target_id"));
//屏蔽非文件类
if (!e.dataTransfer.files.length) {
return ;
}
var files = e.dataTransfer.files;
var ImageURL = files[0];
if (ImageURL.type != 'image/jpeg' && ImageURL.type != 'image/png') {
return ;
}
var size = ImageURL.size/1024/1024; //M
if (size > 20) {
return ;
}
var reader = new FileReader();
reader.onload = function(e) {
//console.log(this.result);
document.getElementsByTagName('img')[0].src = this.result;
}
reader.readAsDataURL(ImageURL);
});
事件
class EventDispatcher {
addEventListener( type, listener ) {
if ( this._listeners === undefined ) this._listeners = {};
const listeners = this._listeners;
if ( listeners[ type ] === undefined ) {
listeners[ type ] = [];
}
if ( listeners[ type ].indexOf( listener ) === - 1 ) {
listeners[ type ].push( listener );
}
}
hasEventListener( type, listener ) {
if ( this._listeners === undefined ) return false;
const listeners = this._listeners;
return listeners[ type ] !== undefined && listeners[ type ].indexOf( listener ) !== - 1;
}
removeEventListener( type, listener ) {
if ( this._listeners === undefined ) return;
const listeners = this._listeners;
const listenerArray = listeners[ type ];
if ( listenerArray !== undefined ) {
const index = listenerArray.indexOf( listener );
if ( index !== - 1 ) {
listenerArray.splice( index, 1 );
}
}
}
dispatchEvent( event ) {
if ( this._listeners === undefined ) return;
const listeners = this._listeners;
const listenerArray = listeners[ event.type ];
if ( listenerArray !== undefined ) {
event.target = this;
// Make a copy, in case listeners are removed while iterating.
const array = listenerArray.slice( 0 );
for ( let i = 0, l = array.length; i < l; i ++ ) {
array[ i ].call( this, event );
}
event.target = null;
}
}
}
export { EventDispatcher };
事件(Event)列表
- HTML DOM 事件:https://www.runoob.com/jsref/dom-obj-event.html
- 各种事件列表:https://developer.mozilla.org/zh-CN/docs/Web/Events
鼠标/触摸/触摸屏事件
判断四个移动方向: 以起点为基准建立平面坐标系,连接起点与鼠标实时终点为直线,通过判断该直线与X轴之间角度计算方位,且以45度角为方向分割线。除此之外,设立一个边界值,一旦终点超过该边界值,则忽略向上或向下的方向,默认为超过边界值前的方向。
解决mouse与touch触发click的问题
// 禁止mouse与touch时触发click
let mouseTouchMoved = false;
const targetEl = [Element];
targetEl.addEventListener('touchstart', function(event) {
mouseTouchMoved = false;
});
targetEl.addEventListener('mousedown', function(event) {
mouseTouchMoved = false;
});
target.addEventListener('mousedown', function(event) {
event.preventDefault();
mouseTouchMoved = true;
});
target.addEventListener('touchmove', function(event) {
event.preventDefault();
mouseTouchMoved = true;
});
/**
* 单击事件
*/
targetEl.addEventListener('click', function(event) {
if (mouseTouchMoved) return;
//
});
鼠标事件
- oncopy
- oncut
- onpaste
- onabort
- onblur
- oncancel
- oncanplay
- oncanplaythrough
- onchange
- onclick
- ondblclick
- onclose
- oncontextmenu
- oncuechange
// 解决双击事件会同时触发单击事件的问题
let click_store;
document.addEventListener('click', function() {
click_store = setTimeout(function(e) {
// 单击事件
}
}, false);
document.addEventListener('dblclick', function() {
clearTimeout(click_store);
// 双击事件
}, false);
// 当用户点击某个对象时调用的事件句柄。
onclick
// 在用户点击鼠标右键打开上下文菜单时触发
oncontextmenu
//去掉默认的contextmenu事件,否则会和右键事件同时出现。
Element.oncontextmenu = function(e){
e.preventDefault();
};
// 当用户双击某个对象时调用的事件句柄。
ondblclick
// 鼠标按钮被按下。
onmousedown
// 当鼠标指针移动到元素上时触发。
onmouseenter
// 当鼠标指针移出元素时触发
onmouseleave
// 鼠标被移动。
onmousemove
// 鼠标移到某元素之上。
onmouseover
// 鼠标从某元素移开。
onmouseout
// 鼠标按键被松开。
onmouseup
触摸事件
触摸事件提供了在触摸屏或触控板上解释手指(或触控笔)活动的能力。
触摸事件: https://developer.mozilla.org/zh-CN/docs/Web/API/Touch_events
ontouchcancel
ontouchmove
ontouchstart
ontouchend
兼容实现方案
// 鼠标/触摸操作事件
this.moveData = {
elW: null, // 元素宽度
elH: null, // 元素高度
elT: null, // 当前元素左边距离文档左侧的偏移量
elL: null, // 当前元素顶部距离文档顶部的偏移量
sX: null, // 鼠标按下x坐标
sY: null, // 鼠标按下y坐标
};
// 视口范围边界
this.viewport = {
x: null,
y: null,
w: null,
h: null,
};
// 鼠标/触摸 按下
Element.addEventListener("mousedown", startEventModel.bind(this), false);
Element.addEventListener("touchstart", startEventModel.bind(this), false);
function startEventModel(e) {
const {
top,
left,
width: elW,
height: elH,
} = currentTarget.getBoundingClientRect();
// 元素尺寸
this.moveData.elW = elW;
this.moveData.elH = elH;
this.moveData.elL = left;
this.moveData.elT = top;
this.moveData.sX = e.x;
this.moveData.sY = e.y;
// 文档尺寸
const { x, y, width, height } = document.body.getBoundingClientRect();
this.viewport.x = x;
this.viewport.y = y;
this.viewport.w = width;
this.viewport.h = height;
}
// 鼠标/触摸 移动
Element.addEventListener("mousemove", mouseMoveEvent.bind(this), false);
Element.addEventListener('touchmove', touchMoveEvent.bind(this), { passive: false });
function mouseMoveEvent(e) {
moveEventModel(e);
}
function touchMoveEvent(e) {
// 阻止了默认的触摸行为,如页面滚动,以便更好地控制触摸反馈
e.preventDefault();
moveEventModel(e)
}
function moveEventModel(e) {
if (this.moveData.sX === null || this.moveData.sY === null) return;
// 开始方向判断
const distanceX = e.x - this.moveData.sX + this.moveData.elL;
const distanceY = e.y - this.moveData.sY + this.moveData.elT;
let _distanceX = distanceX;
let _distanceY = distanceY;
// 约束视口边界
const { x, y, w, h } = this.viewport;
if (distanceX < x) {
_distanceX = x;
}
if (distanceX + this.moveData.elW > w) {
_distanceX = w - this.moveData.elW;
}
if (distanceY < y) {
_distanceY = y;
}
if (distanceY + this.moveData.elH > h) {
_distanceY = h - this.moveData.elH;
}
const style = transform(_distanceX, _distanceY);
moveEl.setAttribute("style", style);
// 开始方向判断
// 向上
// 向下
// 向左
// 向右
}
// 鼠标/触摸 释放
Element.addEventListener("mouseup", endEventModel.bind(this), false);
Element.addEventListener("touched", endEventModel.bind(this), false);
function endEventModel(e) {
if (this.moveData.sX === null || this.moveData.sY === null) return;
// 清空鼠标/触摸记录
this.moveData.sX = null;
this.moveData.sY = null;
}
function transform(x, y) {
return `transform:translate(${x}px, ${y}px);-ms-transform:translate(${x}px, ${y}px);-webkit-transform:translate(${x}px, ${y}px);-moz-transform:translate(${x}px, ${y}px);`;
}
键盘事件
// 某个键盘按键被按下。
onkeydown
// 某个键盘按键被按下并松开。
onkeypress
// 某个键盘按键被松开。
onkeyup
> 键盘快捷键列表(Windows、macOS、Linux):https://developer.mozilla.org/zh-CN/docs/Tools/Keyboard_shortcuts
# 基础事件
> onkeydown 某个键盘按键被按下。 2
> onkeypress 某个键盘按键被按下并松开。 2
> onkeyup 某个键盘按键被松开。
> onclick 当用户点击某个对象时调用的事件句柄。
> ondblclick 当用户双击某个对象时调用的事件句柄。
> oncontextmenu 在用户点击鼠标右键打开上下文菜单时触发
> onmousemove 鼠标被移动。
> onmousedown 鼠标按钮被按下。
> onmouseup 鼠标按键被松开。
> onmouseenter 当鼠标指针移动到元素上时触发。
> onmouseleave 当鼠标指针移出元素时触发
> onmouseover 鼠标移到某元素之上。
> onmouseout 鼠标从某元素移开。
> onscroll
> onmousewheel 该事件在鼠标滚轮在元素上下滚动时触发
window.addEventListener('keydown', keyBoards, false);
function keyBoards(event) {
const e = event || window.event;
console.log(e);
aloneKey(e);
ctrlKey(e);
shiftKey(e);
altKey(e);
ctrlShiftKey(e);
}
// 单键
function aloneKey(e) {
const key = e.key.toLowerCase();
switch(key) {
case key === 'escape': // Escape退出键
break;
case key === 'tab': //
break;
case key === 'capslock': //
break;
case key === 'escape': //
break;
default:
//
}
}
// 组合键:Ctrl +
function ctrlKey(e) {
const ctrl = e.ctrlKey.toLowerCase();
const key = e.key.toLowerCase();
if (!ctrl) return;
switch(key) {
case key === 'a': // 全选:Ctrl + A
break;
case key === 's': // 保存:Ctrl + S
break;
case key === 'z': // 撤回:Ctrl + Z
break;
case key === 'x': // 剪贴:Ctrl + X
break;
case key === 'c': // 复制:Ctrl + C
break;
case key === 'v': // 粘贴:Ctrl + V
break;
case key === 'arrowup': // 上
break;
case key === 'arrowright': // 右
break;
case key === 'arrowdown': // 下
break;
case key === 'arrowleft': // 左
break;
case key === '': //
break;
default:
//
}
}
// 组合键:Shift +
function shiftKey(e) {
const shift = e.shiftKey.toLowerCase();
const key = e.key.toLowerCase();
if (!shift) return;
switch(key) {
case key === '': //
break;
default:
//
}
}
// 组合键:Alt +
function altKey(e) {
const alt = e.altKey.toLowerCase();
const key = e.key.toLowerCase();
if (!alt) return;
switch(key) {
case key === '': //
break;
default:
//
}
}
// 组合键:Ctrl + Shift +
function ctrlShiftKey(e) {
const ctrl = e.ctrlKey.toLowerCase();
const shift = e.shiftKey.toLowerCase();
const key = e.key.toLowerCase();
if (!ctrl || !shift) return;
switch(key) {
case key === '': //
break;
default:
//
}
}
框架/对象(Frame/Object)事件
// 图像的加载被中断。 ( <object>)
onabort
// 在文档加载之前运行脚本
onbeforeonload
// 该事件在即将离开页面(刷新或关闭)时触发
onbeforeunload
// 在加载文档或图像时发生错误。 ( <object>, <body>和 <frameset>)
onerror
// 该事件在当前 URL 的锚部分发生修改时触发。
onhashchange
// 一张页面或一幅图像完成加载。
onload
// 用户退出页面。 ( <body> 和 <frameset>)
onunload
// 该事件在用户访问页面时触发
onpageshow
window.addEventListener('pageshow', function(event) {
console.log('after , pageshow :',event);
});
window.addEventListener('pageshow', function (event) {
if (
event.persisted|| window.performance &&
window.performance.navigation.type == 2
) {
document.getElementById("enter").style.opacity=0
location.reload();
}
});
// 该事件在用户离开当前网页跳转到另外一个页面时触发
onpagehide
window.addEventListener('pagehide', function(event) {
console.log('after , pageshow :',event);
});
// 窗口或框架被重新调整大小。
onresize
> onredo 当文档执行再执行操作(redo)时运行脚本
> onundo 当文档执行撤销时运行脚本
// onpagethide 到 onbeforeunload 到 unload
> 执行顺序:onpagethide 到 onbeforeunload 到 unload
> 触发时机:刷新,跳转,关闭都会触发
onscroll
当文档被滚动时发生的事件。
window.onscroll = function(e) {
var e = e || window.event;
var tar = e.target || e.srcElement;
var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
var windowHeight = document.documentElement.clientHeight || document.body.clientHeight;
var scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight;
}
表单事件
剪贴板事件
// 该事件在用户拷贝元素内容时触发
oncopy
// 该事件在用户剪切元素内容时触发
oncut
// 该事件在用户粘贴元素内容时触发
onpaste
打印事件
// 该事件在页面已经开始打印,或者打印窗口已经关闭时触发
onafterprint
// 该事件在页面即将开始打印时触发
onbeforeprint
拖动事件
// 该事件在元素正在拖动时触发
ondrag
// 该事件在用户完成元素的拖动时触发
ondragend
// 该事件在拖动的元素进入放置目标时触发
ondragenter
// 该事件在拖动元素离开放置目标时触发
ondragleave
// 该事件在拖动元素在放置目标上时触发
ondragover
// 该事件在用户开始拖动元素时触发
ondragstart
// 该事件在拖动元素放置在目标区域时触发
ondrop
动画事件
CSS 动画播放时,会发生以下三个事件,分别是animationstart、animationiteration、animationend。
animationstart
该事件在 CSS 动画开始播放时触发。
// Chrome, Safari 与 Opera 语法
El.addEventListener("webkitAnimationStart", myScript);
// 标准语法
El.addEventListener("animationstart", myScript);
animationiteration
该事件在 CSS 动画重复播放时触发。
// Chrome, Safari 和 Opera 代码
El.addEventListener("webkitAnimationIteration", myStartFunction);
// 标准语法
El.addEventListener("animationiteration", myStartFunction);
animationend
该事件在 CSS 动画结束播放时触发。
// Chrome, Safari 和 Opera 代码
El.addEventListener("webkitAnimationEnd", myStartFunction);
// 标准语法
El.addEventListener("animationend", myStartFunction);
过渡事件
// 该事件在 CSS 完成过渡后触发。
transitionend
指针事件
微软提出的 Pointer Event 则是希望能把所有的指针事件都统一来做管理、让程序开发时能更简单地使用。Pointer Event (W3C)能支持的指针装置包括了鼠标、触控(手指)、以及笔型装置;而除了能整合不同类型的指针装置外,Pointer Event 针对能支持的硬件,也多了相当多额外的参数,像是压力、宽度、高度,甚至比型装置的倾斜程度等等。提供一个让其他浏览器也能支持 Pointer Event 的 JavaScript 函式库、Hand.js
// Pointer Event 总共定义了八种事件,其列表如下:
pointerdown
pointerup
pointercancel
pointermove
pointerover
pointerout
pointerenter
pointerleave
其他事件
// 该事件通过或者从对象(WebSocket, Web Worker, Event Source 或者子 frame 或父窗口)接收到消息时触发
onmessage
// 该事件在浏览器开始在线工作时触发。
ononline
// 该事件在浏览器开始离线工作时触发。
onoffline
// 该事件在窗口的浏览历史(history 对象)发生改变时触发。
onpopstate
// 该事件当 <menu> 元素在上下文菜单显示时触发
onshow
// 该事件在 Web Storage(HTML 5 Web 存储)更新时触发
onstorage
// 该事件在用户打开或关闭 <details> 元素时触发
ontoggle
onhashchange
onload: 当文档加载时运行脚本
onunload
> ononline 该事件在浏览器开始在线工作时触发。
> onoffline 该事件在浏览器开始离线工作时触发。
onreadystatechange
onstop
visibilitychange
该事件用于检测当前页面的可见性状态是否发生变化,当用户导航到新页面、切换标签页、关闭标签页、最小化或关闭浏览器,或者在移动设备上从浏览器切换到不同的应用程序时,该事件就会触发,其 visibilityState 为 hidden。
DataBound
onafterupdate 在数据绑定对象更新数据源对象中的数据后发生。
onbeforeupdate 在数据绑定对象更新数据源对象中的数据之前发生。
oncellchange 当数据源对象中的数据发生更改时发生在数据源对象上。
ondataavailable 每次当新的数据块从源可用时发生。
ondatasetchanged 当初始数据集或新数据集可用时,发生在数据源对象上。
ondatasetcomplete 当数据源对象的所有数据都可用时发生在数据源对象上。
onrowenter 当数据源对象中的当前行已更改时发生在数据源对象上。
onrowexit 在数据源对象中的当前行更改之前发生在该对象上。
onrowsdelete 在删除行之前发生在数据源对象上。
onrowsinserted 插入行后发生在数据源对象上。
devicemotion
// 注意兼容的问题
// https://blog.csdn.net/greenwishing/article/details/90258584
window.addEventListener('devicemotion', (event) => {
// 加速度(需要设备陀螺仪支持)
event.acceleration
// DeviceOrientationEvent.absolute 只读:用来说明设备是提供的旋转数据是否是绝对定位的布尔值。
// DeviceOrientationEvent.alpha 只读:一个表示设备绕z轴旋转的角度(范围在0-360之间)的数字
// DeviceOrientationEvent.beta 只读:一个表示设备绕x轴旋转(范围在-180到180之间)的数字,从前到后的方向为正方向。
// DeviceOrientationEvent.gamma 只读:一个表示设备绕y轴旋转(范围在-90到90之间)的数字,从左向右为正方向。
// 重力加速度(包括重心引力9.8)
const gravity = event.accelerationIncludingGravity; // 由用户引起的设备的加速度和由重力加速度的总和。
gravity.x // 表示x轴(西到东)上的加速度
gravity.y // 表示y轴(南到北)上的加速度
gravity.z // 表示z轴(下到上)上的加速度
// (alpha,beat,gamma);旋转速度
event.rotationRate
// 获取的时间间隔
event.interval
}, false);
横竖屏重力感应的易用组件 | orienter.js
GitHub:https://github.com/shrekshrek/orienter 见devicemotion事件
滚轮事件 onwheel
滚轮事件替换了已被弃用的非标准onmousewheel事件(IE,chrome,safari 浏览器)与DOMMouseScroll(firefox浏览器)。
滚轮事件:https://developer.mozilla.org/zh-CN/docs/Web/API/Element/wheel_event
注意:wheel方法在部分手机上不兼容,需要考虑利用scroll补充。
代码示例
// detect available wheel event
support = "onwheel" in document.createElement("div") ? "wheel" : // 各个厂商的高版本浏览器都支持"wheel"
document.onmousewheel !== undefined ? "mousewheel" : // Webkit 和 IE一定支持"mousewheel"
"DOMMouseScroll"; // 低版本firefox
document.addEventListener('wheel', function(e) {
const { } = e.target.getBoundingClientRect();
console.log(e);
// 滚动方向:e.deltaY > 0(向上) | e.deltaY < 0(向下)
// 滚动增量:e.deltaX | e.deltaY | e.deltaZ
// 滚轮上下
const isUpDown = e.wheelDelta || e.detail;
// 向上,兼容fixrefox e.detail < 0
if (e.wheelDelta > 0 || e.detail < 0) {
//
}
// 向下,兼容fixrefox e.detail > 0
if (e.wheelDelta < 0 || e.detail > 0) {
//
}
// 滚轮左右
const isLeftRight = e.deltaX;
if (window.addEventListener) {
window.addEventListener('DOMMouseScroll', this.mousewheelHandler(), false);
} else {
window.onmousewheel = document.onmousewheel = this.mousewheelHandler();
}
window.onmousewheel = document.onmousewheel = this.mousewheelHandler();
}, false);
document.addEventListener("wheel", (e) => {
e.returnValue = false;
e = e || window.event;
console.log(e);
if (!this.value) return;
if (e.ctrlKey && this.watermark) {
e.returnValue = false;
return;
}
console.log(e);
}, false);
移动端事件
触摸双击事件
// 解决移动端不支持双击事件的问题
let clickCount = 0;
document.addEventListener('touchstart', function(e) {
clickCount ++;
window.setTimeout(function() {
clickCount = 0;
}, 300);
if (clickCount >= 2) {
console.log(e);
}
}, false);
移动端click点击延迟
FastClick
FastClick 是一个简单,易于使用的JS库用于消除在移动浏览器上触发click事件与一个物理Tap(敲击)之间的300延迟。
if ('addEventListener' in document) {
document.addEventListener('DOMContentLoaded', function() {
FastClick.attach(document.body);
}, false);
}
// 或者
var attachFastClick = require('fastclick');
attachFastClick(document.body);
其他方面
网页无障碍体验
访问路径设计规范
语义化规范
读屏测试规范
信息无障碍实践 aria-haspopup 属性 懒加载内容占位容器将 tab-index
- 京东首页实践方案
- 第一阶段,语义化一切 tab 可及的元素——包含页面外跳转链接的 a 标签统一添加 aria-label 属性,以便读屏软件能够简化读取元素信息
- 第二阶段,保证页面主要模块的访问——懒加载内容占位容器将 tab-index 设置为大于 0 的值,使得 tab 键能够遍历到,以便触发页面懒加载,避免 tab 直接跳过
- 第三阶段,扩展带弹出浮层等元素的操作——针对无障碍增加弹出浮层交互逻辑,入口增加 aria-haspopup 属性,告诉读屏软件这里是弹出浮层的入口,将 tab-index 设置为大于 0 的数值使得 tab 操作可聚焦到,浮层弹出后焦点自动聚焦至浮层;
- 第四阶段,为视障用户额外增加快捷跳转——参考 Google 搜索结果页,可在页面的顶部,增加一些隐藏的快捷跳转。PC 首页本次对搜索框以及底部的“为你推荐”位置增加了隐藏跳转链接,只有使用键盘操作的用户能够定位到。