HTML Basic Notes
Emmet
嵌套操作
孩子 : >
.
<!-- div>ul>li -->
<div>
<ul>
<li></li>
</ul>
</div>
兄弟 : +
.
<!-- div+ul>li -->
<div></div>
<ul>
<li></li>
</ul>
上级:^
.
<!-- ul>li^div -->
<ul>
<li></li>
</ul>
<div></div>
ul>li>a^^div
<ul>
<li><a href=""></a></li>
</ul>
<div></div>
重复: *
.
<!-- ul>li*3 -->
<ul>
<li></li>
<li></li>
<li></li>
</ul>
分组:()
.
<!-- div>(p>span)*2 -->
<div>
<p><span></span></p>
<p><span></span></p>
</div>
属性操作
- id:
#
. - class:
.
.
<!-- div#header+div.main+div#footer -->
<div id="header"></div>
<div class="main"></div>
<div id="footer"></div>
属性值: []
.
<!-- a[title=test target=_self] -->
<a title="test" target="_self" href=""></a>
数列值:$
.
<!-- p.item$*3 -->
<p class="item1"></p>
<p class="item2"></p>
<p class="item3"></p>
<!-- p.item$$*3 -->
<p class="item01"></p>
<p class="item02"></p>
<p class="item03"></p>
数列操作符:@
<!-- p.item$@-*3 @- = -1 -->
<p class="item3"></p>
<p class="item2"></p>
<p class="item1"></p>
<!-- p.item$@3*3 @3 = 从3开始3次 -->
<p class="item3"></p>
<p class="item4"></p>
<p class="item5"></p>
<!-- p.item$@-3*3 @-3 = 3次后到3结束 -->
<p class="item5"></p>
<p class="item4"></p>
<p class="item3"></p>
字符操作
字符操作:{}
.
<!-- a{click} -->
<a href="">click</a>
<!-- a>{click}+span{me} -->
<a href="">click<span>me</span></a>
缺省元素
.header+.footer
->div.header+div.footer
.ul>.item*3
->ul>li.item*3
.table>.row*4>.cell*3
->table>tr.row*4>td.cell*3
.
Structure
Section
必须含有hx标题子标签.
Header
Not only can the page <body>
contain a header,
but also can every <article>
and <section>
element.
Footer
Not only can the page <body>
contain a footer,
but also can every <article>
and <section>
element.
hgroup
nav
- 传统导航条
- 侧边栏导航
- 页内跳转
- 翻页操作
main
- 每个网页只有 1 个
main
元素 main
不可为article
、aside
、header
、footer
、nav
孩子
address
联系信息 - QQ、住址 、电子邮箱、主页链接
aside
名词解释的附属部分/友情链接/广告
blockquote
长文本引用
pre
代码段
Head
meta
data list in
HEAD.
Favicon
<head>
<link rel="shortcut icon" type="image/x-icon" href="favicon.ico" />
</head>
Generating favicons in all necessary sizes:
<head>
<link rel="icon" type="image/png" href="/favicon-32x32.png" />
<link rel="apple-touch-icon" sizes="48x48" href="/favicon-48x48.png" />
<link rel="apple-touch-icon" sizes="72x72" href="/favicon-72x72.png" />
<link rel="apple-touch-icon" sizes="96x96" href="/favicon-96x96.png" />
<link rel="apple-touch-icon" sizes="256x256" href="/favicon-256x256.png" />
<link rel="apple-touch-icon" sizes="384x384" href="/favicon-384x384.png" />
<link rel="apple-touch-icon" sizes="512x512" href="/favicon-512x512.png" />
<link rel="manifest" href="/manifest.webmanifest" crossorigin="anonymous" />
</head>
Theme Color
<meta
name="theme-color"
content="#319197"
media="(prefers-color-scheme: light)"
/>
<meta
name="theme-color"
content="#872e4e"
media="(prefers-color-scheme: dark)"
/>
Form
Form:
<form action="表单提交的后台地址接口" method="post" 提交方式,一般为post>
<fieldset 若内容比较多,用来分区>
<legend>这是分区的标题</legend>
<label for="file">选择照片按钮</label>
<input type="file" id="file" />
</fieldset>
<fieldset>
<legend>这是分区的标题</legend>
<div>选择尺寸:</div>
<input
type="checkbox"
多选框
name="size"
数据名称,交给后台
value="5"
值
id="cb_0"
checked
disabled
默认勾选,无法更改
/>
<label for="cb_0">5寸</label>
<!-- 一个input一个label,一一对应,同类name相同 -->
<input type="radio" 单选框 name="material" value="fs" id="rd_0" />
<label for="rd_0">富士,单选第一个</label>
<input
type="text"
单行文本框,默认
id="description"
placeholder="里面是提示"
value="这里是默认内容"
readonly只读
hidden隐藏
/>
<input type="submit" 提交按钮 /> == <button type="submit">提交</button>
<input type="reset" 重置按钮 /> == <button type="reset">重置</button>
<div>
<label for="delivery" 功能提示信息,通过for与标签对应>配送方式</label>
<select id="delivery" 下拉选择>
<optgroup label="group1" 给选项分组>
<option value="0">快递</option>
<option value="1">EMS</option>
</optgroup>
<option value="2" selected>平邮</option>
</select>
</div>
<div>
<label for="feedback">意见反馈,多行文本框</label>
<textarea name="feedback" rows="4" 4行 id="feedback"></textarea>
</div>
</fieldset>
</form>
Form Validation
const usernameInput = document.querySelector('[name="name"]')
usernameInput.addEventListener('invalid', () => {
usernameInput.setCustomValidity('Please enter your name.')
})
Form Element Attributes
Form Attribute
form=form_name
:
使表单元素可放置于表单之外
Form Action Attribute
formaction=target_name
:
使表单元素可提交到不同页面
Form Method Attribute
formmethod=post/get
:
使表单元素以不同的方式提交
Form Enctype Attribute
enctype
(HTTP Content-Type
header):
- 默认值:
application/x-www-form-urlencoded
, 提交前编码所有字符. multipart/form-data
不编码字符, 上传控件表单元素必须使用改值.text/plain
: 表单元素数据中的空格编码为+
.
<form method="post" enctype="multipart/form-data">
<div>
<label for="file">Choose file to upload</label>
<input type="file" id="file" name="file" multiple />
</div>
<div>
<button>Upload image</button>
</div>
</form>
Form Target Attribute
target
定义表单提交后加载页面打开方式:
self
(default): 在相同的框架中打开被链接文档.blank
: 在新窗口中打开被链接文档.parent
: 在父框架集中打开被链接文档.top
: 在整个窗口中打开被链接文档.frameName
: 在指定的框架中打开被链接文档.
Form Novalidate Attribute
取消表单元素的提交验证 (novalidate
):
将 submit
元素的 formnovalidate
属性值为 true
,
使整个表单提交验证失效, 实现假提交,
进而弹出再次确认按钮 (真提交).
AutoFocus Attribute
<div class="form-control">
<label for="search">Search the site...</label>
<input
id="search"
name="search"
type="search"
placeholder="Search here ..."
autofocus
/>
</div>
Required Attribute
<div class="form-control">
<label for="film">The film in question?</label>
<input
id="film"
name="film"
type="text"
placeholder="e.g. King Kong"
required
aria-required="true"
/>
</div>
Disabled and Hidden Attribute
disabled
表单元素的数据不会被提交.hidden
表单元素的数据仍会被提交.
AutoComplete Attribute
email
.new-password
.current-password
.street-address
.address-line1
.address-line2
.address-line3
.city
.state
.country
.tel
.zip
.one-time-code
.cc-name
.cc-number
.cc-exp
.off
.
<form>
<div>
<label for="email">Email</label>
<input autocomplete="email" required type="email" id="email" name="email" />
</div>
<div>
<label for="password">Password</label>
<input
autocomplete="new-password"
type="password"
id="password"
name="password"
/>
</div>
<button>Sign up</button>
</form>
<form>
<div>
<label for="email">Email</label>
<input autocomplete="email" required type="email" id="email" name="email" />
</div>
<div>
<label for="password">Password</label>
<input
autocomplete="current-password"
type="password"
id="password"
name="password"
/>
</div>
<button>Sign in</button>
</form>
Form Labels
指定表单元素的标签
<label for="input_id">OS : </label>
隐式 Control 属性
javascript tips:通过 control 属性改变标签对应表单元素的值
const textbox = $('#label_id').control
textbox.value = '666666' // 等同于 input.value = '666666';
Form Input
Input Types
<!-- default -->
<input type="text" />
<!-- numeric keyboard -->
<input type="tel" />
<!-- numeric keyboard -->
<input type="number" />
<!-- displays @ key -->
<input type="email" />
<!-- displays .com key -->
<input type="url" />
<!-- displays search button -->
<input type="search" />
<!-- displays date picker or wheel controls -->
<input type="date" />
<input type="date picker(data,month,week,time,datetime,datetime-local)" />
<input type="range" />
<input type="color" />
Text Input
<input type="text" spellcheck="true" lang="en" />
Radio Input
name
相同时, 多个 radio 组成一个 radio group.
Checkbox Input
Search Input
搜索条:
<div class="form-control">
<label for="search">Search the site...</label>
<input
id="search"
name="search"
type="search"
placeholder="Search here ..."
/>
</div>
Tel Input
电话号码无输入检查:
<div class="form-control">
<label for="tel">Telephone (so we can berate you if you're wrong)</label>
<input
id="tel"
name="tel"
type="tel"
placeholder="1-234-546758"
autocomplete="off"
required
/>
</div>
Url Input
<div class="form-control">
<label for="web">Your Web address</label>
<input id="web" name="web" type="url" placeholder="https://www.mysite.com" />
</div>
Email Input
<div class="form-control">
<label for="email">Your Email address</label>
<input
type="email"
id="email"
name="email"
placeholder="dwight.schultz@gmail.com"
required
/>
</div>
Number Input
<div class="form-control">
<label for="yearOfCrime">Year Of Crime</label>
<input
id="yearOfCrime"
name="yearOfCrime"
type="number"
min="1929"
max="2015"
step="1"
required
/>
</div>
Range Input
<div class="form-control">
<input
id="howYouRateIt"
name="howYouRateIt"
type="range"
min="1"
max="10"
value="5"
onchange="showValue(this.value)"
/>
<span id="range">5</span>
</div>
DateTime Input
[type]
:
date
.month
.week
.time
.datetime-local
.
<input id="date" name="date" type="date" />
<input id="month" name="month" type="month" />
<input id="week" name="week" type="week" />
<input id="time" name="time" type="time" />
Color Input
<div class="form-control">
<label for="color">Your favorite color</label>
<input id="color" name="color" type="color" />
</div>
List Input
autocomplete
, 为输入框指定智能提示数据:
<div class="form-control">
<label for="awardWon">Award Won</label>
<input id="awardWon" name="awardWon" type="text" list="awards" />
<datalist id="awards">
<select>
<option value="Best Picture"></option>
<option value="Best Director"></option>
<option value="Best Adapted Screenplay"></option>
<option value="Best Original Screenplay"></option>
</select>
</datalist>
</div>
File Input
File type:
<input type="file" accept=".jpeg,.png" />
Multiple files:
<input type="file" multiple />
Capture device camera:
<!-- Front camera -->
<input type="file" capture="user" accept="image/*" />
<!-- Back camera -->
<input type="file" capture="environment" accept="image/*" />
Indeterminate
检查 [type=checkbox]
的状态:
if (checkbox.indeterminate) {
doSomething()
} else {
if (checkbox.checked)
doSomething()
else
doSomething()
}
Pattern
通过正则表达式指定输入格式:
<input pattern="[0-9][A-Z]{3}" />
Validity
返回 ValidityState
对象, 拥有 ValidityState.valid
属性.
Output
<input>
元素的镜像元素.
Textarea
Maxlength
Cols
每行可显示字符最大数
Wrap
- hard:换行时加入换行标志,此时必须指定
cols
属性 - soft:不加入换行标志
Menu
Dialog
Native dialog
:
<dialog open>
<p>Greetings, one and all!</p>
<form method="dialog">
<button>OK</button>
</form>
</dialog>
<button class="btn" data-toggle="#dialog">Open modal</button>
<button class="btn" data-toggle="#dialog-tall">Open tall modal</button>
<dialog id="dialog">
<header>
Example modal
<button class="btn btn-close" data-close>
<svg width="16" height="16"><use xlink:href="#x" /></svg>
</button>
</header>
Some basic text inside the modal to demonstrate how it all looks and works.
</dialog>
<dialog id="dialog-tall">
<header>
Super tall modal
<button class="btn btn-close" data-close>
<svg width="16" height="16"><use xlink:href="#x" /></svg>
</button>
</header>
<p>Line breaks to push the height out.</p>
<button type="button" class="btn" data-close>Close</button>
</dialog>
const togglers = document.querySelectorAll('[data-toggle]')
const closers = document.querySelectorAll('[data-close]')
togglers?.forEach((toggler) => {
const target = toggler.getAttribute('data-toggle')
const dialogs = document.querySelectorAll(target)
toggler.addEventListener('click', (_event) => {
dialogs.forEach((dialog) => {
dialog.showModal()
})
})
})
closers?.forEach((closer) => {
closer.addEventListener('click', (_event) => {
const dialog = closer.closest('dialog')
dialog.close()
})
})
Datalist
<datalist id="register-prompt" style="display: none">
<option value="Windows">Windows</option>
<option value="Mac OS">Mac OS</option>
<option value="Linux">Linux</option>
</datalist>
<label for="myBrowser">Choose a browser from this list:</label>
<input list="browsers" id="myBrowser" name="myBrowser" />
<datalist id="browsers">
<option value="Chrome"></option>
<option value="Firefox"></option>
<option value="Internet Explorer"></option>
<option value="Opera"></option>
<option value="Safari"></option>
<option value="Microsoft Edge"></option>
</datalist>
Form Demo
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Sign Up Form</title>
<link rel="stylesheet" href="css/normalize.css" />
<link
href="https://fonts.googleapis.com/css?family=Lato:300,400,700"
rel="stylesheet"
/>
<link rel="stylesheet" href="css/main.css" />
</head>
<body>
<form action="index.html" method="post">
<h2>Your basic info</h2>
<label for="name">Name</label>
<input type="text" id="name" name="student_name" />
<label for="mail">Email</label>
<input type="email" id="mail" name="student_email" />
<label for="password">Password</label>
<input type="password" id="password" name="student_password" />
<label>Age:</label>
<input
type="radio"
id="under_16"
value="under_16"
name="user_age"
/><label for="under_16" class="light">Under 16</label><br />
<input type="radio" id="over_16" value="over_16" name="user_age" /><label
for="over_16"
class="light"
>16 or Older</label
>
<h2>Your profile</h2>
<label for="bio">Biography</label>
<textarea id="bio" name="student_bio"></textarea>
<label for="courses">Select Courses</label>
<select id="courses" name="student_courses">
<optgroup label="Engineering">
<option value="computer_engineering">
Computer Science Engineering
</option>
<option value="electrical_engineering">Electrical Engineering</option>
<option value="mechanical_engineering">Mechanical Engineering</option>
<option value="civil_engineering">Civil Engineering</option>
<option value="chemical_engineering">Chemical Engineering</option>
</optgroup>
<optgroup label="Management">
<option value="finance_management">Finance Management</option>
<option value="technology_management">Technology Management</option>
<option value="marketing_management">Marketing Management</option>
<option value="business_administration">
Business Administration
</option>
</optgroup>
</select>
<label>Interests:</label>
<input
type="checkbox"
id="engineering"
value="interest_engineering"
name="user_interest"
/><label class="light" for="engineering">Engineering</label><br />
<input
type="checkbox"
id="business"
value="interest_business"
name="user_interest"
/><label class="light" for="business">Business</label><br />
<input
type="checkbox"
id="law"
value="interest_law"
name="user_interest"
/><label class="light" for="law">Law</label>
<button type="submit">Submit</button>
</form>
</body>
</html>
Content
Details
Accordion list:
<div class="container">
<h3>FAQ</h3>
<details open>
<summary>Why is it called an accordion menu?</summary>
<hr />
<p>
Because each part of it can expand and contract, like in an accordion. If
you don't know what an accordion is, just imagine a cute fluffy cat. You
still won't know what it is, but at least you'll feel better about not
knowing.
</p>
</details>
<details>
<summary>Huh?</summary>
<hr />
<p>Huh.</p>
</details>
<details>
<summary>If I use an accordion menu will it make me cool?</summary>
<hr />
<p>
No, not unless you're designing a MySpace profile. The
<code>{"details"}</code> element is cool though, and you can use that for
a lot of things. I'm using it on this page right below here, to show the
code for each example!
</p>
</details>
</div>
<style>
.container {
padding: 1em 2em;
border: 0.2em solid black;
border-radius: 2em;
}
details {
padding: 1em;
margin-bottom: 1em;
border: 0.1em solid black;
border-radius: 1em;
}
summary {
font-size: 1.2em;
cursor: pointer;
}
summary::-webkit-details-marker {
display: none;
}
details[open] summary {
font-size: 1.3em;
}
</style>
Details Summary
展开与收缩时触发 toggle
事件:
<details>
<summary>Details</summary>
Something small enough to escape casual notice.
</details>
Details Data Grid
Details AutoComplete
Details Open
默认 open=false
.
Description List
<dl>
: description list.<dt>
: description Term.<dd>
: description details.
<h1>Review your data</h1>
<p>
Please review the data you entered in the previous step to ensure it is
correct:
</p>
<dl>
<dt>First name</dt>
<dd>Marc</dd>
<dt>Last name</dt>
<dd>Simmons</dd>
<dt>Date of Birth</dt>
<dd><time datetime="1990-05-15">May 15 1990</time></dd>
</dl>
Table
<table>
<thead>
<tr>
<th scope="col">Col Header 1</th>
<th scope="col">Col Header 2</th>
<th scope="col">Col Header 3</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Row Header 1</th>
<td>Row 1 Col 2</td>
<td>Row 1 Col 3</td>
</tr>
<tr>
<th scope="row">Row Header 2</th>
<td>Row 2 Col 2</td>
<td>Row 2 Col 3</td>
</tr>
</tbody>
<tfoot>
<tr>
<th scope="row">Summary</th>
<td>Col 2 summary</td>
<td>Col 3 summary</td>
</tr>
</tfoot>
</table>
Mark
突出/高亮显示,无关原文作者
Ins
Insert text
<ins
cite="https://bugzilla.mozilla.org/show_bug.cgi?id=1620467"
datetime="2020-07-23"
>
The <code>appearance</code> property, previously only available prefixed in
Firefox, can now be used in all modern browsers un-prefixed.
</ins>
Del
Delete text
<del
cite="https://bugzilla.mozilla.org/show_bug.cgi?id=1620467"
datetime="2020-07-23"
>
Firefox doesn't support CSS's standard <code>appearance</code> property, so
you can only use it prefixed.
</del>
U
underline text
Em
文章重点
Strong
段落强调
Small
- 免责声明、注意事项、法律规定、版权声明
- 不改变文字样式
Hr
下划线
Progress
value/max 百分比
<label for="file">File progress:</label>
<progress id="file" max="100" value="70">70%</progress>
Meter
Value
Min
Max
Low
High
Optimum
Wbr
软换行
Media
Figure
流内容: 如代码、文件、图片、音频、视频.
Figcaption
<figure>
可拥有唯一的 0
/1
个 <figcaption>
:
<figure aria-labelledby="image-alt">
<img src="/media/cc0-images/elephant-660-480.jpg" alt="Elephant at sunset" />
<figcaption id="image-alt">An elephant at sunset</figcaption>
</figure>
Image
Src
Alt
(图片崩溃时文本)、title(提示信息)、class(CSS 类选择器)
Loading
<img src="picture.jpg" loading="lazy" />
Responsive Images
<!-- `img` element -->
<img src="foo" alt="bar" />
<!-- `img` element, `srcset` attribute -->
<img
srcset="foo-320w.jpg 320w, foo-480w.jpg 480w, foo-800w.jpg 800w"
sizes="(max-width: 480px) 440px, 320px"
src="foo-320w.jpg"
alt="bar"
/>
Picture
- Multiple
<source>
and only one<img>
<!-- `picture` and `source` elements, `srcset` attributes -->
<picture>
<source media="(max-width: 799px)" srcset="foo-480w.jpg" />
<source media="(min-width: 800px)" srcset="foo-800w.jpg" />
<img src="foo-800w.jpg" alt="bar" />
</picture>
- Multiple width images
<picture>
<source srcset="128px.jpg, 256px.jpg 2x, 512px.jpg 3x" />
<img src="foo.jpg" alt="bar" />
</picture>
- Multiple type images
<picture>
<source srcset="foo.avif" type="image/avif" />
<source srcset="foo.webp" type="image/webp" />
<img src="foo.jpg" />
</picture>
SVG
const svgElement = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
const svgRectElement = document.createElementNS(
'http://www.w3.org/2000/svg',
'rect'
)
Embed
Embed best practice:
<script src="lazySizes.min.js" async></script>
<iframe
data-src="https://www.youtube.com/embed/aKydtOXW8mI"
width="560"
height="315"
class="lazyload"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write;
encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
>
</iframe>
Video
<video
src="myVideo.mp4"
width="640"
height="480"
controls
autoplay
preload="auto"
loop
poster="myVideoPoster.png"
>
If you're reading this either video didn't load or your browser is legacy!
</video>
<video
width="640"
height="480"
controls
preload="auto"
loop
poster="myVideoPoster.png"
>
<source src="myVideo.sp8" type="video/super8" />
<source src="myVideo.mp4" type="video/mp4" />
<p><b>Download Video:</b> MP4 Format:<a href="myVideo.mp4">"MP4"</a></p>
</video>
Anchor
Anchor Href
[href]
超链接指向超链接
/#id
/#name
:
<a href="https://github.com">Link</a> <a href="#title">Link</a>
Anchor ID
当前锚点标识.
Anchor Name
当前锚点名字.
Anchor Target
定义被链接文档出现方式:
self
: 默认方式, 在相同的框架中打开被链接文档.blank
: 在新窗口中打开被链接文档.parent
: 在父框架集中打开被链接文档.top
: 在整个窗口中打开被链接文档.framename
: 在指定的框架中打开被链接文档.
Command
Information
Popover
<button popovertarget="my-popover">Toggle Popover</button>
<div id="my-popover" popover>Popover Content</div>
<button popovertarget="my-popover" class="toggle-btn">Toggle Popover</button>
<div id="my-popover" popover="manual">
<p>I am a popover with more information.</p>
<p>
<button
popovertarget="my-popover"
popovertargetaction="hide"
class="close-btn"
>
<span aria-hidden="true">❌</span>
<span class="sr-only">Close</span>
</button>
</p>
</div>
Time
Pub Date
pubdate
:
boolean 代表当前<time>
表示整个网页的时间
DateTime
<time datetime="2010-11-13T20:00Z"></time>
<time datetime="2010-11-13T20:00+09:00"></time>
T
分隔日期与时间Z
使用 UTC 标准时间+
时差
Attributes
Dataset
<td data-row="1" data-column="1"></td>
function onChange(event) {
const {
currentTarget: {
dataset: { row, column },
},
} = event
}
Global Attributes
Global attributes are attributes common to all HTML elements, they can be used on all elements, though they may have no effect on some elements:
accesskey
.autocapitalize
.autofocus
.contenteditable
: boolean.dir
.draggable
.enterkeyhint
.hidden
: boolean.inert
.inputmode
.is
.itemid
.itemprop
.itemref
.itemscope
.itemtype
.lang
.nonce
.popover
.spellcheck
: boolean.style
.tabindex
.title
.translate
.on*
event handler content attributes.
DOM defines the user agent requirements
for the class
, id
, and slot
attributes for any element in any namespace.
The class
, id
, and slot
attributes may be specified on all HTML elements.
Tabindex
-1: 编程可获得焦点,tab 键不可获得焦点
HTML Attributes and DOM Properties
DOM Properties Differences
HTML attributes vs DOM properties differs in:
- HTML serialization: attributes serialize to HTML, whereas properties don't.
- Value types: attribute values are always strings, whereas properties can be any type.
- Case sensitivity: attribute names are case-insensitive, whereas property names are case-sensitive.
// 1. HTML serialization:
const div = document.createElement('div')
div.setAttribute('foo', 'bar')
div.hello = 'world'
console.log(div.outerHTML) // '<div foo="bar"></div>'
// 2. Value types:
const div = document.createElement('div')
const obj = { foo: 'bar' }
div.setAttribute('foo', obj)
console.log(typeof div.getAttribute('foo')) // 'string'
console.log(div.getAttribute('foo')) // '[object Object]'
div.hello = obj
console.log(typeof div.hello) // 'object'
console.log(div.hello) // { foo: 'bar' }
// 3. Case sensitivity:
// <div id="test" HeLlO="world"></div>
const div = document.querySelector('#test')
console.log(div.getAttributeNames()) // ['id', 'hello']
div.setAttribute('FOO', 'bar')
console.log(div.getAttributeNames()) // ['id', 'hello', 'foo']
div.TeSt = 'value'
console.log(div.TeSt) // 'value'
console.log(div.test) // undefined
DOM properties come with validation and defaults, whereas HTML attributes don't:
- Omit invalid value:
input.type
. - Normalize boolean value:
details.open
. - Convert incoming value to number and coerce negative values to 0:
img.height
.
// Omit invalid type:
const input = document.createElement('input')
console.log(input.getAttribute('type')) // null
console.log(input.type) // 'text'
input.type = 'number'
console.log(input.getAttribute('type')) // 'number'
console.log(input.type) // 'number'
input.type = 'foo'
console.log(input.getAttribute('type')) // 'foo'
console.log(input.type) // 'text'
// Normalize non-empty string to true value:
const details = document.querySelector('details')
console.log(details.getAttribute('open')) // ''
console.log(details.open) // true
details.open = false
console.log(details.getAttribute('open')) // null
console.log(details.open) // false
details.open = 'hello'
console.log(details.getAttribute('open')) // ''
console.log(details.open) // true
DOM Properties Reflection
For convenience, most specs will create a property equivalent for every defined attribute.
Here's the spec for <ol>
.
The Content attributes
section defines the HTML attributes
(reversed
, start
, type
),
and the DOM interface
defines the DOM properties:
interface OListElement extends HTMLElement {
reversed: boolean
start: long
type: DOMString
};
If attribute (e.g foo=bar
) isn't a spec-defined attribute,
then there isn't a spec-defined foo
property that reflects it:
const div = document.querySelector('div[foo=bar]')
console.log(div.getAttribute('foo')) // 'bar'
console.log(div.foo) // undefined
div.foo = 'hello world'
console.log(div.getAttribute('foo')) // 'bar'
console.log(div.foo) // 'hello world'
input.defaultValue
and input.value
propertyinput.defaultValue
property
reflects HTML value
attribute,
input.value
property
doesn't reflect any attribute:
class HTMLInputElement extends HTMLElement {
get defaultValue() {
return this.getAttribute('value') ?? ''
}
set defaultValue(newValue) {
this.setAttribute('value', String(newValue))
}
#value = undefined
get value() {
return this.#value ?? this.defaultValue
}
set value(newValue) {
this.#value = String(newValue)
}
// This happens when the associated form resets
formResetCallback() {
this.#value = undefined
}
}
// <input type="text" value="default" />
const input = document.querySelector('input')
console.log(input.getAttribute('value')) // 'default'
console.log(input.value) // 'default'
console.log(input.defaultValue) // 'default'
input.defaultValue = 'new default'
console.log(input.getAttribute('value')) // 'new default'
console.log(input.value) // 'new default'
console.log(input.defaultValue) // 'new default'
// Here comes the mode switch:
input.value = 'hello!'
console.log(input.getAttribute('value')) // 'new default'
console.log(input.value) // 'hello!'
console.log(input.defaultValue) // 'new default'
input.setAttribute('value', 'another new default')
console.log(input.getAttribute('value')) // 'another new default'
console.log(input.value) // 'hello!'
console.log(input.defaultValue) // 'another new default'
Accessibility
Semantic HTML
- Semantical HTML section guide.
- Semantic HTML presentation.
- Semantic search element.
Structure Accessibility
- Semantics section reference.
<header>
:role="banner"
<nav>
:role="navigation"
<main>
:role="main"
<aside>
:role="complementary"
<section>
:role="region"
<article>
:role="article"
<footer>
:role="contentinfo"
<header>
<nav>
<ul>
<li><a></a></li>
</ul>
</nav>
</header>
<main>
<section></section>
</main>
<footer></footer>
Heading Accessibility
- 7 heading levels:
<div role="heading" aria-level="7"></div>
- One
<h1>
per page
Navigation Accessibility
- Have a HTML sitemap.
- Support keyboard navigation (Key and Tab Index).
- Breadcrumbs a11y:
aria-label="breadcrumbs"
aria-label="page"
<nav aria-label="breadcrumbs">
<ol>
<li>
<a href="https://example.com/"> Home </a>
</li>
<li>
<a href="https://example.com/products"> Products </a>
</li>
<li>
<a href="https://example.com/products/childrens-clothing">
Children's clothing
</a>
</li>
<li>
<a
href="https://example.com/products/childrens-clothing/shoes"
aria-current="page"
>
Shoes
</a>
</li>
</ol>
</nav>
Section Accessibility
<section aria-labelledby="sectionHeader1">
<h2 id="sectionHeader1">A great section</h2>
</section>
<section aria-labelledby="sectionHeader2">
<h2 id="sectionHeader2">An even better section</h2>
</section>
Article Accessibility
The <article>
element is used to represent a fully self-contained region of content
<article>
<header>
<h1>Why you should buy more cheeses than you currently do</h1>
</header>
<section>
<header>
<h2>Part 1: Variety is spicy</h2>
</header>
<!-- cheesy content -->
</section>
<section>
<header>
<h2>Part 2: Cows are great</h2>
</header>
<!-- more cheesy content -->
</section>
</article>
Reference Accessibility
<cite>
<q>
<blockquote>
<code>
<p>
Every time Kenny is killed, Stan will announce
<q cite="http://en.wikipedia.org/wiki/Kenny_McCormick#Cultural_impact">
Oh my God, you/they killed Kenny! </q
>.
</p>
<blockquote cite="https://www.huxley.net/bnw/four.html">
<p>
Words can be like X-rays, if you use them properly – they'll go through
anything. You read and you're pierced.
</p>
</blockquote>
<cite>– Ados Huxley, Brave New World</cite>
Link Accessibility
<article>
<h2 id="article1-title">My article</h2>
<p>Article brief description with truncation...</p>
<a href="article1-url" aria-labelledby="article1-title">Read more</a>
</article>
Text Accessibility
<b>
<strong>
<mark>
<ins>
<del>
<abbr>
: 专有名词解释<abbr title="HyperText Markup Language">HTML</abbr>
不要将 <b>
元素与 <strong>
、<em>
或 <mark>
元素混淆:
<strong>
元素表示某些重要性的文本<em>
强调文本<mark>
元素表示某些相关性的文本
Text Color A11Y
- Devtool inspect elements A11Y for color contrast ratio.
- Don't forget
::selection
.
Text Spacing A11Y
line-height
of blocks of text should be 1.5.- space between paragraphs should be 1.5 times the
line-height
(so a minimum of2.25 rem
). - Line height (line spacing) to at least 1.5 times the font size.
- Spacing following paragraphs to at least 2 times the font size.
- Letter spacing (tracking) to at least 0.12 times the font size.
- Word spacing to at least 0.16 times the font size.
Button Accessibility
Use <button>
for clickable elements
Image Accessibility
alt=""
SVG Accessibility
<title>
<desc>
<svg width="100" height="75">
<title>Dark rectangle</title>
<desc>A grey rectangle with rounded corners and a dark green border</desc>
<rect
width="75"
height="50"
rx="20"
ry="20"
fill="#666"
stroke="#229b23"
stroke-fill="1"
/>
</svg>
Figure Accessibility
<figure aria-labelledby="image-alt">
<img src="" alt="" />
<br />
<figcaption id="image-alt"></figcaption>
</figure>
Audio Source Accessibility
src=""
type=""
Form Accessibility
Group Related Fields
With fieldset
and legend
:
<form role="form">
<fieldset>
<legend>Choose one of these three items:</legend>
<input id="one" type="radio" name="items" value="one" />
<label for="one">Choice One</label><br />
<input id="two" type="radio" name="items" value="two" />
<label for="two">Choice Two</label><br />
<input id="three" type="radio" name="items" value="three" />
<label for="three">Choice Three</label>
</fieldset>
</form>
Input Accessibility
label[for]
input.aria-label
andaria-describedby
for input hint.aria-invalid
for error input.aria-hidden
for hidden input.
<form role="form">
<label for="name">Name:</label>
<input id="name" name="name" type="text" />
</form>
<form role="form">
<label for="name">Name:</label>
<span class="prefix-input">
<span class="prefix-icon" id="name-icon" aria-label="Input Prefix Icon">
<icon />
</span>
<input id="name" name="name" type="text" aria-describedby="name-icon" />
</span>
</form>
<form role="form">
<label for="email-address"> Your Email Address </label>
<span id="email-error">
Error: Your email address must contain an @ symbol
</span>
<input
id="email-address"
name="email-address"
type="email"
aria-describedby="email-error"
aria-invalid="true"
/>
</form>
export default function Field() {
return (
<>
<div className="user-code-field">
<input
id="userCode"
aria-describedby={
errors.userCode ? 'user-code-error' : 'user-code-help'
}
/>
<span id="user-code-help" className="user-code-help">
Enter your 4 digit user code
</span>
</div>
{errors.userCode && (
<div id="user-code-error" role="alert" className="error">
You must enter your 4 character user code
</div>
)}
</>
)
}
Time Accessibility
<time datetime="2016-09-15">Thursday, September 15<sup>th</sup></time>
Address Accessibility
<footer>
<section class="contact" vocab="http://schema.org/" typeof="LocalBusiness">
<h2>Contact us!</h2>
<address property="email">
<a href="mailto:us@example.com">us@example.com</a>
</address>
<address property="address" typeof="PostalAddress">
<p property="streetAddress">123 Main St., Suite 404</p>
<p>
<span property="addressLocality">Your Town</span>,
<span property="addressRegion">AK</span>,
<span property="postalCode">12345</span>
</p>
<p property="addressCountry">United States of America</p>
</address>
</section>
</footer>
Color Contrast
- more than 4.5:1 ratio
Keys and Tabindex Accessibility
<a id="second" href="" accesskey="c"></a>
document.addEventListener('keyup', (event) => {
switch (event.keyCode) {
// escape
case 27:
// exit
break
// enter || space bar
case 13 || 32:
// submit or something
break
// left arrow
case 37:
// move back / previous
break
// right arrow
case 39:
// move forward
break
// up arrow
case 38:
// move up
break
// down arrow
case 40:
// move down
break
default:
throw new Error('Unsupported key!')
}
})
/**
* Traps the tab key inside of the context, so the user can't accidentally get
* stuck behind it.
*
* Note that this does not work for VoiceOver users who are navigating with
* the VoiceOver commands, only for default tab actions. We would need to
* implement something like the inert attribute for that (see https://github.com/WICG/inert)
* @param {object} e the Event object
*/
export function trapTabKey(e, context) {
if (e.key !== 'Tab')
return
const focusableItems = getFocusable(context)
const focusedItem = document.activeElement
const focusedItemIndex = focusableItems.indexOf(focusedItem)
if (e.shiftKey) {
if (focusedItemIndex === 0) {
focusableItems[focusableItems.length - 1].focus()
e.preventDefault()
}
} else {
if (focusedItemIndex === focusableItems.length - 1) {
focusableItems[0].focus()
e.preventDefault()
}
}
}
Self-Closing Tags
Self-closing tags (<tag />
) do not exist in HTML.
If a trailing / (slash) character is present in the start tag of an HTML element,
HTML parsers ignore that slash character:
<div>This text is inside the div.</div>
<div />This text is inside the div.
<input />This text is outside the input.
<input>This text is outside the input.</input>
Further reading:
ARIA
Web Accessibility Initiative - Accessible Rich Internet Applications:
aria-label
.aria-labelledby="dropdownMenuButton"
: dropdown/form>.aria-describedBy
: input + small.
<label id="l1" for="f3">label text</label>
<input type="text" id="f3" aria-labelledby="l1 l2" />
<p>other content</p>
<span tabindex="-1" id="l2">more label text</span>
<div aria-describedby="test">text</div>
<div id="test" role="tooltip">tooltip text</div>
<div role="dialog" aria-label="login" aria-describedby="log1">
<div id="log1" tabindex="-1">Provide user name and password to login.</div>
</div>
aria-disabled="true"
: disable element.aria-hidden="true"
.aria-controls="navbarSupportedContent"
: navigation/select.aria-expanded="false"
: dropdown.aria-haspopup="true"
: dropdown/popup.aria-current="pages
: breadcrumb.aria-valuenow
/aria-valuemin
/aria-valuemax
: progress.- role.
<header>
:role="banner"
.<nav>
:role="navigation"
.<main>
:role="main"
.<section>
:role="region"
.<article>
:role="article"
.<aside>
:role="complementary"
.<footer>
:role="contentinfo"
.<form>
:role="form"
.- 7th heading level:
<div role="heading" aria-level="7"></div>
. role="button"
.role="checkbox"
.role="gridcell"
.role="link"
.role="menuitem"
.role="menuitemcheckbox"
.role="menuitemradio"
.role="option"
.role="progressbar"
.role="radio"
.role="scrollbar"
.role="searchbox"
.role="separator (when focusable)"
.role="slider"
.role="spinbutton"
.role="switch"
.role="tab"
.role="tabpanel"
.role="textbox"
.role="tooltip"
.role="treeitem"
.role="presentation"
: removes the semantics of an element. If set an interactive or focusable element torole="presentation"
, assistive technology user will not know what it is or how to use it.role="application"
.
<button
class="list-expander"
aria-expanded="false"
aria-controls="expandable-list-1"
>
Expand List
</button>
<ul id="expandable-list-1">
<li><a href="http://example.com">Sample Link</a></li>
<li><a href="http://example.com">Sample Link 2</a></li>
<li><a href="http://example.com">Sample Link 3</a></li>
</ul>
const listExpander = document.querySelector('.list-expander')
const list = document.querySelector('#expandable-list-1')
listExpander.addEventListener('click', (e) => {
if (list.getAttribute('aria-expanded') === 'true')
list.setAttribute('aria-expanded', 'false')
else
list.setAttribute('aria-expanded', 'true')
})
Dialog ARIA Role
<div id="dialog_layer" class="dialogs">
<div
id="dialog1"
role="dialog"
aria-labelledby="dialog1_label"
aria-describedby="dialog1_desc"
aria-modal="true"
class="hidden"
>
<h2 id="dialog1_label" class="dialog_label">Address Added</h2>
<p id="dialog1_desc" class="dialog_desc">
The address you provided has been added to your list of delivery
addresses. It is ready for immediate use. If you wish to remove it, you
can do so from
<a href="#" onclick="openDialog('dialog2', this)"> your profile. </a>
</p>
<div class="dialog_form_actions">
<button type="button" id="dialog1_close_btn" onclick="closeDialog(this)">
OK
</button>
</div>
</div>
<div
id="dialog2"
role="dialog"
aria-labelledby="dialog2_label"
aria-describedby="dialog2_desc"
aria-modal="true"
class="hidden"
>
<h2 id="dialog2_label" class="dialog_label">End of the Road!</h2>
<p id="dialog2_desc" class="dialog_desc">
You activated a fake link or button that goes nowhere! The link or button
is present for demonstration purposes only.
</p>
<div class="dialog_form_actions">
<button type="button" id="dialog2_close_btn" onclick="closeDialog(this)">
Close
</button>
</div>
</div>
</div>
HTML First over ARIA
<!--div role="banner"-->
<header></header>
<!--div role="navigation"-->
<nav></nav>
<!--div role="main"-->
<main></main>
<!--div role="region"-->
<section [accessible name]></section>
<!--div role="complementary"-->
<aside></aside>
<!--div role="contentinfo"-->
<footer></footer>
<!--div role="form"-->
<form></form>
<div role="search"></div>
Accessibility Best Practice
A11y audit list:
- Keyboard-only navigation.
- Voice control.
- Screen reader.
- High contrast mode.
- Dark mode.
- Browser zoom.
- Don't use
aria-hidden
on the<body>
element. - Complete meta header:
- Add missing languages.
- Make sure
document
has atitle
element. - Tool:
react-helmet
.
- Fix low text contrast: 确保文本与其背景保持足够的对比.
- 不要将颜色作为传达信息的唯一手段 (色盲/弱).
- Add missing alternative text.
- Remove empty links and buttons.
- 注意表单:
- Add missing labels.
- 提供输入焦点的视觉提示.
- 避免组件识别障碍.
- Make sure
IDs
andKeys
of elements are unique. - Required context role.
- Required aria attribute.
- Valid aria attribute.
Accessibility Checklist
- W3C ARIA Usage Rule
- WebAIM WCAG (Web Content Accessibility Guidelines) 2 Checklist
- A11Y Project
- A11Y 101
Accessibility Tools
Accessibility Reference
WAI-ARIA
: improve website’s accessibility withWAI-ARIA
.ARIA
: W3C official examples.WebAIM
: Million project.- A11y community: learn about a11y.
- Cognitive a11y resources.
- A11y for PDF.
- A11y for design system.
- Assistive technology for readers.