元素

元素分类

可替换元素 VS 非替换元素

可替换元素

CSS 里,可替换元素(replaced element)的展现不是由 CSS 来控制的。这些元素是一类外观渲染独立于 CSS 的外部对象。 典型的可替换元素有imgobjectvideo和表单元素,如textareainput。某些元素只在一些特殊情况下表现为可替换元素,例如audiocanvas。 通过 CSS content 属性来插入的对象,被称作匿名可替换元素(anonymous replaced elements)。

CSS 在某些情况下会对可替换元素做特殊处理,比如计算外边距和一些auto值。

需要注意的是,一部分(并非全部)可替换元素,本身具有尺寸和基线(baseline),会被像vertical-align之类的一些 CSS 属性用到。

块级元素 VS 行内元素

块级元素

块级元素生成一个(默认情况下)元素框,填充其父元素的内容区域,并且在其两侧不能有其他元素,即它在元素框之前和之后生成“断行”。

行内元素

差别

分类/比较点行内元素块级元素
内容一般情况下,行内元素只能包含数据和其他行内元素块级元素可以包含行内元素和其他块级元素(这种结构上的包含继承区别可以使块级元素创建比行内元素更”大型“的结构)
格式默认情况下,行内元素不会以新行开始,而块级元素会新起一行默认情况下,块级元素会新起一行
大小widthheightpadding-top/bottommargin-top/bottom都不可控制,padding-left/rightmargin-left/right可以控制widthheightpaddingmargin都可控制
高度

元素操作

查找元素

DOM 提供了查找元素的能力。比如:

  • querySelector
  • querySelectorAll
  • getElementById
  • getElementsByName
  • getElementsByTagName
  • getElementsByClassName

需要注意,以下的这几个 API 的性能都高于querySelector

  • getElementById
  • getElementsByName
  • getElementsByTagName
  • getElementsByClassName

此外,以下的这几个 API 获取的集合并非数组,而是一个能够动态更新的集合。

  • getElementsByName
  • getElementsByTagName
  • getElementsByClassName
<div class="wind-stone"></div>

<script>
const divs = document.getElementsByClassName('wind-stone');
console.log(divs.length); // 1
const div = document.createElement('div');
div.setAttribute('class', 'wind-stone')
document.documentElement.appendChild(div)
console.log(divs.length); // 2
</script>
1
2
3
4
5
6
7
8
9
10

特别的元素

iframe 元素

若是将src不为空的iframe元素设置成display: none;,再次将display属性设置为非none属性值时,会导致iframe重新加载网页。

幽灵空白节点

在HTML5文档声明下,块状元素内部的内联元素的行为表现,就好像块状元素内部还有一个(更有可能两个-前后)看不见摸不着没有宽度没有实体的空白节点,这个假想又似乎存在的空白节点,我称之为“幽灵空白节点”。-- 张鑫旭 - CSS深入理解vertical-align和line-height的基友关系open in new window

影响

<div class="ctn">
  <img src="./example.jpg">
</div>
1
2
3
.ctn {
  background-color: red;
}
1
2
3

幽灵空白节点导致图片下方产生空隙

幽灵空白节点会继承父元素的font-sizeline-height,而默认的line-height取值是normal,一般约为1.2倍的font-size(具体取决于元素的font-family),导致幽灵空白节点所占据位置的最下方与节点的基线位置的距离是超过0的。

且内联元素vertical-align默认取值为baseline,而示例中图片作为替换元素,其baseline是图片的底部,幽灵空白节点的baseline是字符的基线,因此导致图片下方产生空隙,空隙的高度就是幽灵空白节点占据位置的最下方与节点内字符基线位置的距离。

清除幽灵空白节点

清除幽灵空白节点影响的方式有如下几种:

  • vertical-align失效
img {
  display: block;
}
1
2
3

PS: vertical-align只用来指定行内元素(inline)或表格单元格(table-cell)元素的垂直对齐方式,对块级元素无效。

  • vertical-align取其他值,如top/middle/bottom
img {
  vertical-align: middle;
}
1
2
3
  • 直接修改line-height
.ctn {
  line-height: 1px;
}
1
2
3

图片下方的空隙高度,实际上是文字计算后的行高值和字母x下边缘的距离。因此,只要行高足够小,实际文字占据的高度的底部就会在x的上面,下面没有了高度区域支撑,自然,图片就会有容器底边贴合在一起了,比如设置为1px0

此外,若是.ctn元素没有设置line-height属性,则line-height的默认值是相对于font-size的相对值,我们也可以设置font-size: 0来间接将line-height设置为0

.ctn {
  font-size: 0;
}
1
2
3

清除幽灵节点产生的空隙

利用幽灵空白节点垂直居中

.ctn {
  line-height: 300px;
}
img {
  vertical-align: middle;
}
1
2
3
4
5
6

垂直居中

需要注意的是,这里的居中并不是绝对的垂直居中,会略有一些偏差,只要再给.ctn元素添加font-size: 0;就可以实现绝对的垂直居中了,原因详见张鑫旭 - CSS深入理解vertical-align和line-height的基友关系open in new window

a 标签

文件下载

download 属性

download属性无法使用重命名下载文件名

浏览器的兼容性问题:

  • Firefox 考虑到安全问题,该下载文件必须是从自己的服务器或域名中的,否则将在浏览器中打开。
  • 在 Chrome 和 Opear 中,如果说下载文件不是在子集的服务器或域名中,这些浏览器会忽视download属性,换句话来说,文件名不变。
  • 其他浏览器还不支持

Reference: <a>标签中download属性无法使用重命名下载文件名怎么解决?open in new window

结合 a 标签的 download 属性 + window.open

<a>标签的download属性浏览器兼容性不好,因此可以先判断浏览器是否支持。支持的话,使用download属性;否则,使用window.open打开新页面下载。

根据以上描述,基于 Vue 2.0+ 创建了download-link组件,调用方式如下:

  • 仅显示文本
<download-link
  :url="excelTemplateUrl"
  text="素材 Excel 文件规范"
  name="素材 Excel 文件规范"
/>
1
2
3
4
5
  • 显示 slot
<download-link
  :url="excelTemplateUrl"
  name="素材 Excel 文件规范"
>
  <span>素材 Excel 文件规范</span>
</download-link>
1
2
3
4
5
6

其中,

  • url:下载链接
  • name:下载时默认的文件名称(download属性支持且不跨域,此字段才有效)
  • text<a>显示的文本

vue 组件

<template>
    <a
        v-if="isSupportDownload"
        class="download-link"
        :href="url"
        :download="name"
    >
        <slot v-if="$slots.default" />
        <span
            v-else
            v-text="text"
        />
    </a>
    <a
        v-else
        class="download-link"
        href="#"
        @click.stop.prevent="download(url)"
    >
        <slot v-if="$slots.default" />
        <span
            v-else
            v-text="text"
        />
    </a>
</template>

<script>
const isSupportDownload = 'download' in document.createElement('a');

export default {
    name: 'DownloadLink',
    props: {
        url: {
            type: String,
            required: true
        },
        name: {
            type: String,
            default: '下载文件'
        },
        text: {
            type: [String, Number],
            required: true
        }
    },
    data() {
        return {
            isSupportDownload
        };
    },
    methods: {
        download(url) {
            window.open(url, '_blank', 'fullscreen=no,width=400,height=300');
        }
    }
};
</script>

<style lang="scss" scoped>
.download-link {
    text-decoration: none;
    color: #409eff;
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65

script 元素

script 元素内的脚本执行

在页面执行script元素内的脚本内容时,通过document.getElementsByTagName('script')获取所有script标签,只能获得已经执行和正在执行的script元素,而当前正在执行的script元素之后的其他script元素都无法获取。

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, viewport-fit=cover" />
        <title>script 测试</title>
    </head>
    <body>
        <script id="script-0">
          console.log('第一个 script');
        </script>
        <script id="script-1" type="text/javascript" data-hello="您好!" src="./outer.js"></script>
        <script id="script-2">
          console.log('inline script \n')
          var scripts2 = document.getElementsByTagName('script');

          [...scripts2].forEach(script => {
            console.log('  ', script.id)
          })
        </script>
    </body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// outer.js
console.log('outer script \n')
var scripts = document.getElementsByTagName('script');

[...scripts].forEach(script => {
  console.log('  ', script.id)
})
1
2
3
4
5
6
7
// 页面加载并执行后的结果:

// 第一个 script
// outer script
//  script-0
//  script-1
// inline script
//  script-0
//  script-1
//  script-2
1
2
3
4
5
6
7
8
9
10

我们知道,浏览器里 JavaScript 脚本的执行是单线程的,而script元素的执行也是从上而下的。

在上面的示例里,当outer.js加载并执行时,获取整个文档里script的数量,只有 2 个,即script-0script-1,而不能获取到内联脚本script-2

这种情况,跟将script元素放在body元素内的头部位置,并在script里获取其之后的元素但获取不到的情况是一样的,因为此时后面的 DOM 元素还没有渲染出来。

获取 script 元素的特性

在示例里,我们为了打印方便,给每个script标签添加了id特性。但是在实际项目里,某个外链的script可能想获取到该script元素上的所有特性,但不是通过id的方式(除非第三方外链脚本强制要求调用者在script元素上添加特定id特性)。

<script id="script-1" type="text/javascript" data-hello="您好!" src="./outer.js"></script>
1

针对上面这种情况,我们可以这样做:

var scripts = document.getElementsByTagName('script');

var self = scripts[scripts.length - 1];

[...self.attributes].forEach(attr => {
  console.log(`name: ${attr.name}, value: ${attr.value}`)
})

// 打印结果:
// name: id, value: script-1
// name: type, value: text/javascript
// name: data-hello, value: 您好!
// name: src, value: ./log.js
1
2
3
4
5
6
7
8
9
10
11
12
13

meta 标签

theme-color 设置浏览器导航栏背景颜色

<meta name="theme-color" content="#ff6633">
1

charset 设置文档编码方式

<meta charset="UTF-8">
1

添加了charset属性的meta标签无需再有namecontentcharset属性描述了 HTML 文档自身的编码形式。因此,建议将这个标签放在head的第一个。这样,浏览器读到这个标签之前,处理的所有字符都是 ASCII 字符,众所周知,ASCII 字符是 UTF-8 和绝大多数字符编码的子集,所以,在读到meta之前,浏览器把文档理解多数编码格式都不会出错,这样可以最大限度地保证不出现乱码。

一般情况下,HTTP 服务端会通过 HTTP 头来指定正确的编码方式,但是有些特殊的情况如使用file协议打开一个 HTML 文件,则没有 HTTP 头,这种时候,charsetmeta就非常重要了。