【本文会持续更新!】
1、color 影响 border-color
当只设置元素的边框宽度和样式时,边框的颜色会取当前元素 color
属性的计算值。
<div style="color: blue;border: 1px solid;">这里设置了 border,但不指定 border-color</div>
瞧瞧该实例元素的 border-color
属性的计算值:
可以看到,border-color
属性未设置值时为 initial
关键字,代表取该属性的默认值。那么,为什么这里取的是 color
属性的值而非浏览器对 border-color
的默认值(如果有的话)呢?
因为 border-color
属性的默认值就是 currentColor 关键字(CSS 3),也就是当前元素的 color
属性的计算值,详见 border-color | MDN。
color 前景色
MDN 中对 color
属性是这样介绍的:
The
color
property sets the foreground color of an element's text content, and its decorations. It doesn't affect any other characteristic of the element
翻译过来就是,color
属性设置元素文本内容的 前景色 和 修饰。
HTML 元素的前景色包括:
- 字体颜色,也就是狭义上的
color
; -
border-color
,边框的颜色; -
outline-color
,轮廓的颜色; -
box-shadow
,阴影的颜色; -
text-shadow
,文本阴影的颜色;
以及文本修饰中的 text-decoration-color
,下划线的颜色。
<head>
<style>
p {
color: blue;
max-width: 500px;
}
</style>
</head>
<body>
<p>这里没有做任何处理</p>
<p style="border: 1px solid;">这里设置了 border,但不指定 border-color</p>
<p style="outline: 1px solid;">这里设置了 outline,但不指定 outline-color</p>
<p style="text-decoration: underline;">这里设置了 text-decoration,但不指定 text-decoration-color</p>
<p style="box-shadow: 0 1px 2px 0;">这里设置了 box-shadow,但不指定颜色</p>
<p style="text-shadow: 10px 10px 2px;">这里设置了 text-shadow,但不指定颜色</p>
</body>
2、移动端 H5 禁止显示系统菜单
当在移动端 H5 上长按一个目标元素时,浏览器会弹出一个关于目标元素的菜单信息。请看下面针对文本元素的实例:
<span>这是一段文字,请在移动端长按</span>
在某些场景下,我们并不希望浏览器弹出这样的菜单,应该怎么做呢?我在网上搜罗了几个常见方案:
-
通过 -webkit-touch-callout 属性禁用 callout【无效】
-webkit-touch-callout: none;
-webkit-touch-callout
属性的兼容性非常差,之前只有在 Safari 浏览器上可用。而 现在应该是被废弃了,亲测在 ios Safari 浏览器上不起效,Can I use 上也已经搜索不到了。 -
JS 屏蔽 contextmenu 事件的默认行为【无效】
$elm.addEventListener('contextmenu', (e) => { e.preventDefault(); });
当用户尝试打开上下文菜单(通常是鼠标右键单击)时,
contextmenu
事件会被触发,我们可以通过preventDefault()
方法来屏蔽菜单的显示。那么这种方法是否适用于移动端 h5 呢?答案是否定的。不妨来看看
contextmenu
事件的兼容性:so,网上的部分教程就不要再误人子弟了。
-
通过 user-select 属性让元素不可选中【有效】
-webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; -o-user-select: none; user-select: none;
在移动端,若用户不可选中元素,自然也就不能通过长按调起默认菜单了。但需要注意的是,
user-select: none
在部分浏览器(如 Safari)中会使<input>
、<textarea>
等表单元素失效。 -
JS 屏蔽
touchstart
事件的默认行为【有效】$elm.addEventListener('touchend', (e) => { e.preventDefault(); });
此方案和使用
user-select
是一样的原理,但同样 需要注意禁止默认行为所带来的负面影响,譬如作用于可滚动元素时。
所有方案的测试情况见:https://codepen.io/JunreyCen/pen/rEBYPV
总结一下,移动端的兼容性一直是非常棘手的问题,各种手机操作系统、各种浏览器应用没有一套统一的 web 标准,都喜欢 “各抒己见”。也因此,上面提及的方案在某些型号手机的浏览器中(譬如 Oppo 自带的浏览器)依然是不起作用的,这种情况下开发者只能 “见招拆招”,无招可使时也只能择 “较优解” 了。
3、文本溢出显示省略号
我们经常遇到单行文本溢出时显示省略号的场景,那多行文本的类似处理该如何实现?
单行文本
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap; /* 文本不换行 */
多行文本
多行文本溢出时,最后一行截断并显示省略号。这里提供两种实现方式:
-
使用 line-clamp 属性
line-clamp
是一个不规范的属性,没有出现在 CSS 规范草案中。而且,它必须结合旧版的flexbox
(伸缩盒)模型才起作用。overflow: hidden; display: -webkit-box; /* 旧版伸缩盒模型 */ display: -moz-box; -webkit-box-orient: vertical; /* 子元素垂直排列 */ -moz-box-orient: vertical; -webkit-line-clamp: 2; /* 块元素显示的文本行数 */ -moz-line-clamp: 2;
如下图所示,
line-clamp
在兼容性方面还是有点缺陷的。除此之外还需要注意的是,autoprefixer 等预处理插件会自动删除一些旧的样式属性(包括
-webkit-box-orient
属性),所以使用这种方式时要对 autoprefixer 插件进行配置,详见这个 issue 。 -
JS + CSS 手动加省略号
处理流程:
- 元素的
height
值设置为line-height
值的整数倍; - JS 获取元素的文本节点高度(
scrollHeight
),若高度倍line-height
值则视为文本溢出; - 文本溢出时,于元素右下方显示省略号,否则不显示;
<style> #ellipsis { position: relative; display: block; overflow: hidden; line-height: 20px; height: 40px; /* height 为整数倍 line-height */ word-break: break-all; /* 使文本填充满容器,利于省略号和文本的衔接 */ } .show-ellipsis { padding-right: 12px; /* 给省略号腾出空间 */ } .show-ellipsis:after { content: '...'; position: absolute; right: 0; bottom: 0; } </style> <p id="ellipsis">JS 和 CSS 设置多行文本字数超出时最后一行显示省略号,JS 和 CSS 设置多行文本字数超出时最后一行显示省略号</p> <script> const $elm = document.getElementById('js-ellipsis'); const style = window.getComputedStyle($elm); const limitHeight = +style.height.replace('px', '') + +style.lineHeight.replace('px', ''); if ($elm.scrollHeight >= limitHeight) { // 文本溢出则显示省略号 $elm.className = 'show-ellipsis'; } </script>
这种实现方式基本不存在兼容性问题(除了低版本 IE 浏览器),但效果肯定比不上
line-clamp
的方式,只能静待 W3C 制订一套规范的处理方案了。 - 元素的
4、图像自适应
我们写页面的时候经常会遇到,获取的图片尺寸与我们所期待的渲染尺寸不符。譬如在渲染用户头像的场景下,我们往往希望用户上传的图片都是统一的正方形尺寸,然而用户给的可能是这样的:
常见的处理方式是 适当的剪裁使图片填满容器而不被拉伸,我们可以利用背景图(background-size
/ background-position
)来实现:
<style>
.avatar {
width: 200px;
height: 200px;
border: 2px solid #07C160;
background: url('./images/captain.jpeg') no-repeat;
background-size: cover;
background-position: center;
}
</style>
<div class="avatar"></div>
CSS 3 中提供了新的属性:object-fit,可以对 <img>
标签作宽高自适应处理,效果类似于 background-size
属性。
object-fit
提供了5个取值:
- none:内容保持原有尺寸;
- contain:内容保持宽高比地缩放,内容和容器的宽高比不匹配时会 留下白边;
- cover:内容保持宽高比地填满容器,内容和容器的宽高比不匹配时会 被裁剪;
- fill:内容刚好填满容器,内容和容器的宽高比不匹配时会 被拉伸;
- scale-down:内容尺寸和 none 或 contain 中的一个相同,最终会显示尺寸较小的那个;
background-size 和 object-fit 的效果对比
同样的,background-position
和 object-position 的作用类似,比较明显的区别在于:
background-position
的默认值是0% 0%
,而object-position
的默认值是50% 50%
。
object-fit 的意义
- 解放了
background-image
的能力;
譬如可以同时利用background-image
和<img>
标签完成图片的堆叠等。 -
object-fit
对所有 可替换元素 都有效;
常见的可替换元素包括:<iframe>
、<video>
、<embed>
、<img>
、<input type="image">
。
兼容性
额,如果要兼容 IE 浏览器的话,还是乖乖用背景图的方式把。
5、块元素等比缩放
现在的网页开发都讲究响应式设计,以使在各种尺寸的设备上也能保持良好的 UI 呈现。在响应式布局中,我们经常需要实现随网页视窗宽度动态变化的元素,尤其是支持等比缩放的元素。
比较传统的做法,是通过 JS 监听 resize
事件,动态获取容器的宽度然后调整元素的宽高。这里提供一种纯 CSS 实现的方式,主要利用的是 padding
属性的百分比取值:
padding
属性的百分比取值是相对于其包含块的宽度。
所以,当包含块的宽度发生变化时,子元素的 padding
属性的计算值会随之发生变化,也就是说子元素的总宽高都会被改变,且与包含块宽度成正比。
<style>
.wrapper {
width: 200px;
height: 300px;
border: 2px solid #07C160;
}
.content {
padding: 50%;
width: 0;
height: 0;
background-color: #FA5151;
}
</style>
<div class="wrapper">
<div class="content"></div>
</div>
接下来的工作,只需要让目标元素的宽高参考于 padding
子元素的宽高,就完成了元素的等比缩放。我们可以利用 绝对定位 来实现。
这里实现一个宽高比为 1:2、宽度保持为容器的等比缩放元素:
<style>
.wrapper {
width: 20%;
height: 40%;
border: 2px solid #07C160;
}
.container {
position: relative;
padding: 50%;
width: 0;
height: 0;
}
.content {
position: absolute;
top: 0;
left: 0;
width: 50%;
height: 100%;
background-color: #FA5151;
}
</style>
<div class="wrapper">
<div class="container">
<div class="content"></div>
</div>
</div>
为了突出演示效果,稍微 “修饰” 了一下:
需要体验 Demo 的请跳转:https://codepen.io/JunreyCen/pen/agzoyG
6、垂直外边距合并
当两个垂直外边距邻接时,会合并成一个外边距。只有普通文档流中块元素的垂直外边距才会发生合并,不在同一个 BFC 内(譬如行内元素、浮动元素、绝对定位等)的垂直外边距不会合并,水平方向的外边距也不会合并。
通常发生外边距合并的场景有:
-
相邻的两个块元素,邻接的上/下外边距发生合并
发生合并时外边距的计算规则:
- 两个外边距都是正数时,取两者中的较大值;
- 两个外边距一正一负时,取两者之和;
- 两个外边距都是负数时,比较两者的绝对值大小,谁大取谁;
-
无内边距(
padding
)和边框(border
)的父元素的垂直外边距会与子元素的垂直外边距合并 -
无内边距(
padding
)、边框(border
)和内容(content
)的空元素的上/下外边距会合并
这里提供一个完整 Demo:https://codepen.io/JunreyCen/pen/zVxPWX
7、行内(块)元素空隙
试试执行这段代码:
<img src="./images/captain.jpeg" width="200">
<img src="./images/captain.jpeg" width="200">
奇怪,两张图片之间出现了一道缝隙……
这是因为 img
元素默认会被渲染成行内元素(display: inline;
),而上述代码中两个 <img>
之间其实是存在一个 换行符 (以及若干个 制表符)的,这些都会被渲染成一个空白格,也就导致了缝隙的产生。
解决办法有俩:
- 写 HTML 代码的时候避免行内元素间的空格、换行符等特殊字符;
<img src="./images/captain.jpeg" width="200"><img src="./images/captain.jpeg" width="200">
- 把行内元素所在行的字体大小设为
font-size: 0;
<style> body { font-size: 0; } </style> <img src="./images/captain.jpeg" width="200"> <img src="./images/captain.jpeg" width="200">
PS:行内块元素(
display: inline-block
)也会存在同样的问题。
8、pointer-events: none;
pointer-events
属性可以指定元素是否可以成为鼠标事件的 target ,通俗点讲就是该元素是否可以接收鼠标事件。
pointer-events
属性有多种取值,详见 MDN。这里着重介绍取值 none
:
pointer-events: none;
指定元素及其后代元素不会成为鼠标事件的 target,父元素不受影响。
实验 Demo
实验内容:三层 DOM 元素都监听了鼠标点击事件,其中 target 节点设置了 pointer-events: none
,点击 child 元素,看看有哪层元素可以响应点击事件。
<div class="parent" onclick="alert('parent')">
<div class="target" onclick="alert('target')">
<div class="child" onclick="alert('child')"></div>
</div>
</div>
左边不作处理的实例会 alert
三次;而右边的实例只会 alert
一次,内容为 parent
。
应用场景
-
同层元素点击穿透
我们在写 UI 基础组件时,多多少少会接触到 Field 输入框组件。比如下面的实例,需求是满足 0.5px 边框 + input 输入框。
<style> .field { position: relative; width: 250px; height: 50px; text-align: center; } .field:after { position: absolute; top: -50%; left: -50%; right: -50%; bottom: -50%; content: ''; border: 1px solid #ccc; transform: scale(0.5); } </style> <div class="field"> <input type="text" placeholder="请输入"> </div>
你会发现,左边的输入框无法聚焦。这其实跟 0.5px 边框的实现方式有关,实例中利用伪元素
:after
制造了一个2倍尺寸的子元素,然后通过transform: scale(0.5)
缩放,从而实现 0.5px 边框。然而,**这个伪元素和input
输入框属于同层元素,根据渲染的先后顺序伪元素是层叠于输入框之上的,所以鼠标点击事件是无法被输入框捕获的。我们只需要给伪元素设置
pointer-events: none;
,使其无法成为鼠标事件的 target,input
输入框就可以成功被聚焦。 -
阻止 :hover、:active 等鼠标行为状态的触发
设置了
pointer-events: none;
的元素无法响应鼠标事件,自然也就无法触发相关的状态了。
想体验实例的请访问:https://codepen.io/JunreyCen/pen/NZqKOx。
9、:first-child 和 :first-of-type 的区别
:first-child
匹配其父元素的符合特定类型的首个子元素。条件更为苛刻,需要满足 首个子元素 + 符合特定类型。:first-of-type
匹配其父元素的符合特定类型的第一个元素。条件较为宽松,在满足 符合特定类型 的范畴下寻找第一个元素即可。
<style>
.group-1 h2:first-child,
.group-1 h3:first-child,
.group-2 h2:first-of-type,
.group-2 h3:first-of-type {
color: #FA5151;
}
</style>
<div class="group-1">
<h2>父元素的第一个元素,第一个 h2 元素</h2>
<h3>父元素的第二个元素,第一个 h3 元素</h3>
</div>
<div class="group-2">
<h2>父元素的第一个元素,第一个 h2 元素</h2>
<h3>父元素的第二个元素,第一个 h3 元素</h3>
</div>
10、当心分号 ;
我们都知道,在样式表的声明块({...}
)内,样式声明之间会用分号隔开,这是因为引擎在解析时,每条声明之间的空格(包括换行符等)会被忽略:
<style>
div {
color: red;
font-size: 20px;
}
</style>
<div>content</div>
那如果,分号写在声明块之外呢?
这相当于告诉引擎,样式表解析到这里就结束了,后面的东西就不用管了。来看看这个 demo:
<style>
div {color: red};
.ctn-1 {color: blue}
.ctn-2 {font-weight: 600}
</style>
<div id="ctn-1">蓝色字体</div>
<div id="ctn-2">加粗字体</div>
遇到类似的面试题时,就要注意仔细看别被 “坑” 了~