2023-06-01
CSS
00
请注意,本文编写于 544 天前,最后修改于 305 天前,其中某些信息可能已经过时。

目录

常见面试题
CSS常见布局
圣杯布局
双飞翼布局
居中布局
水平居中
垂直居中
水平垂直居中
文字居中
文本内容居中
文字、iconfont、图标对齐
flex布局方案
CSS工具方法
文本溢出
清除浮动的几中方式
修改滚动条样式
CSS模块化方案
CSS Module (css in js)
BEM
基本规则
BEM命名好长
修饰器
原子类和BEM
实际应用中如何解决BEM命令过长
应用方案
样式格式化
移动端rem等比缩放方案
移动端0.5px边框
为什么会有1px问题?
transfrom缩放
背景渐变
svg
响应式布局
水印
Css特效
checkbox特效
border特效
background特效
clip-path 背景图片裁剪
3D变换
transform 变换
滚动条特效-阅读进度条
其他案例
动画
雪碧图动画
keyFrames逐帧动画
为什么CSS动画比JS动画性能好

本文将介绍CSS常见布局,常见工具方法及PC和移动端常见布局方案,另外再介绍一些CSS进阶相关面试题。

常见面试题

  1. 圣杯布局有了解吗?有几种实现方案?
  2. 水平居中有哪些实现方式
  3. 为什么CSS动画比JS动画性能好
  4. 1px边框有哪些解决方案?
  5. 水印的实现方案
  6. CSS模块化有哪些方案?

CSS常见布局

布局这一块代码示例比较多,会导致文章篇幅太长,我这里就不粘贴代码了,会给一个示例链接,点击打开一个新窗口,页面上右击打开检查(浏览器开发者工具)即可以查看布局和调试代码。

圣杯布局

  • 页面分上中下(header, body, footer)三部分,中间区域高度自适应
  • 中间区域又分三部分左中右,中间为内容区宽高自适应

image.png

双飞翼布局

  • 容器高度自适应
  • 左右两栏上下自适应
  • 中间宽度和高度自适应

image.png

居中布局

水平居中

image.png

核心代码如下

css
/* 水平居中核心代码(容器宽度不确定)*/ /* 方案1 */ .box1 { width: 50%; margin: 0 auto; } /* 方案2 */ .box2 { display: table; margin: 0 auto; font-size: 12px; } /*方案三*/ .container3 { position: relative; } .box3 { position: absolute; left: 50%; transform: translateX(-50%); } /*方案四*/ .container4 { overflow: hidden; } .box4 { float: left; margin-left: 50%; transform: translateX(-50%); } /*方案五*/ .container5 { display: flex; justify-content: center; } .box5 { flex: none; display: flex; align-self: flex-start; } /*方案六*/ .container6 { display: grid; justify-content: center; } .box6 { align-self: flex-start; } /*方案七*/ .container7 { display: -webkit-box; -webkit-box-pack: center; -webkit-box-align: start; }

完整案例点这里

垂直居中

image.png

完整案例点这里

水平垂直居中

image.png

完整案例点这里

文字居中

文字居中的方式可以考虑用容器包裹一下,变成上面的居中布局。 本段落所讲的内容是不用标签包裹的情况如何让文字居中。

文本内容居中

  • 水平居中 text-align: center
  • 垂直居中
    • 方案1 line-height
    • 方案2 {display: table-cell; vertical-align: middle;}
  • 水平垂直居中 = 水平居中 + 垂直居中

文字、iconfont、图标对齐

image.png

做过移动端web或小程序开发的同学,可能会遇到过 iconfont、图标与文字(文字小于12px时)在某些android机上对不齐(尤其是三星机)

  • 使用flex或inline-box+vertical-aligin 只能实现近似对齐
  • 使用transform缩放,效果略微好一点,且非常烦碎

Q:为什么会对不齐?

A:简单说跟字体设计有关系,感兴趣可以读一下《Icon和文本对齐方式的探索》,接下来给的方案也是参考这种方式实现的。

html
<style> .box { border: 1px solid red; margin: 20px; display: flex; align-items: center; line-height: 30px; font-size: 12px; font-family: -apple-system-font, Helvetica Neue, Arial, sans-serif; } .box span { display: inline-flex; align-items: center; line-height: 30px; margin: 0 6px; } </style> <!--字符 \u200b 在html中使用 &#x200b 表示--> <div class="box"> <span> &#x200b <svg t="1666104646439" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3319" xmlns:xlink="http://www.w3.org/1999/xlink" width="12" height="12"> <path d="M32 407.584a279.584 279.584 0 0 1 480-194.944 279.584 279.584 0 0 1 480 194.944 278.144 278.144 0 0 1-113.024 224.512L562.592 892.8a96 96 0 0 1-124.416-1.952l-308.16-270.688A278.976 278.976 0 0 1 32 407.584z" p-id="3320"></path> </svg> </span> 左边是iconfont, 右边是图片 三者严格居中 <span> &#x200b <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAAAwhJREFUaEPtmFuoDlEUx3+nSB68eKU8uJRLXpAQuV9zj6KUEHkgp3Q68oCSB4pckkskxQMpopDLOQ8U4sEDSURIkusDUYr+pz217TPfzJ7ZM9/pq9n19X3fzFpr//9rrVlr7WmiwVdTg+OnItDVEawiUEUg0ANVCgU6MFi9ikCwCwMNpEVgLPAJ+Gw+vtv1BcYDf4HbwEdLcQkwBHgOPABe1jA60ci9B9qBb3FycQRmAfqsBHpZSveBg8CZBBYLgBZgjCNzE7gLbDfXReKc+X0Z2GtA6pL0VwODLBtvgd3AIXdvl8B64HCKm68CW4DHjtw+YFOK7g6LxFFgrSUvctOAcQk21gHH7Ps2ga3ATs8ceQWssrwmb8qrPktARWQE8NBHwZEZCLyIrkUE8hj7AqwBlmUAr32fAMMMgNPAiowkNppU7lCLCLjh9LX5B+jmK2zJTTLRmw5cz6hvp2EHgZ7Az4xGQsV3AUpZLVWqLKsTgT7AuywWCpA9bj3AwQSGx1SUAjAmmrgILMwZgZHAI/shVsNoKxuxY1+leDbQA/iVYW/1jHm2vJ6B7sAP853BVpCoGqKqiUqiOrLvGgo8dQno/y1gsq+VAuQ2mK46B7jiae+/1LFTSL/VXLZ5GipCTOXzhhlLlnsYjAUvvagP9AY06wzwMBYqEuXxaOBeirE3wBS787ry9iihsB4IReehP9dKG3uoc1XvAIudSbaTeXeYuwbM8ACRV+SEGT9sfTd9vwNngWbgd9pGLoHBgEpcvzTFHPc1A00FPsTojjLj+zPgEqDU8Vpx54FFwAUv7WxCalxqYIWuWieypNzMA2ApcD6PYppO0pGyqNJaGni7jNYiqhPQkTQvJNwvFbwPAcnMBPZYhxBfPqWD9yUguf7ASWCCB3pNiq2ADvKlr7TXKjYAdWuRmJ+ASgdugf9aOnKzQRYCESal02YH4GtzUD9VL+DRPnkISFcHcZVavQzQG4n9gEjUfeUlUHegtTasCHR1KKoIVBEI9ECVQoEODFavIhDswkADDR+Bf62LejGXewJuAAAAAElFTkSuQmCC" style="height: 16px;"> </span> </div>

注意事项:

  • h5 html标签最好不要带lang属性 如<html lang="zh-CN"> ,否则无法实现Icon与图片及文字之间的对齐,但是这样写也可以<html lang="en">
  • 该实现方式(Flex + Strut hack)也非万能的,与字体存在关系,推荐这样写 font-family: -apple-system-font, Helvetica Neue, Arial, sans-serif;

flex布局方案

flex可以实现很多布局,如 居中布局、骰子布局、网格布局、百分比布局、圣杯布局、流式布局等,具体可以参考阮一峰老师的文章《Flex布局教程:实例篇》

flex布局注意事项

  • aligin-items默认值为stretch 会默认拉伸项目
  • flex-shrink默认值是1 项目会被挤压 为什要强调这一点?应该有小伙伴跟我一样喜欢flex布局,有时重构或优化页面排版时会遇到问题,注意这两点可以提高你的效率

CSS工具方法

文本溢出

css
/*单行文本*/ .oo { white-space:nomal; overflow:hidden; text-overflow:ellipsis; } /*多行文本*/ .muti-lines { display: -webkit-box; overflow: hidden; word-break: break-all; text-overflow: ellipsis; -webkit-box-orient: vertical; -webkit-line-clamp: 2; }

清除浮动的几中方式

浮动产生的原因

父元素的高度无法被撑开,影响与父元素同级的元素,与浮动元素同级的非浮动元素(内联元素)会跟随其后,若非第一个元素浮动,则该元素之前的元素也需要浮动,否则会影响页面的显示。

清除浮动的几种方式

  1. 使用空标签清除浮动 浮动标签后面添加一个空标签 定义css {clear:both}
  2. 使用overflow 给包含浮动元素的父标签添加css属性 {overflow:auto; zoom:1; } zoom: 1 用于兼容IE6
  3. 给浮动元素的父元素的下面一个兄弟元素增加样式 {clear: both;}
  4. after伪元素清除浮动 给浮动元素的父元素增加样式 .clearfix::after{clear: both; content: ''; display:table} 非IE浏览器

修改滚动条样式

scss
div { overflow: auto; height: 100%; /* 滚动条容器样式 */ &::-webkit-scrollbar { width: 5px; } /* 滚动条容器样式 不包含区块 */ &::-webkit-scrollbar-track { background-color: #f0f0f0; } /* 滚动条区块样式 */ &::-webkit-scrollbar-thumb { border-radius: 2px; background-color: #66f; } }

CSS模块化方案

Q:CSS为什么要模块化? A:

  • 1)CSS有语义化的命名约定和CSS层的分离,将有助于它的可扩展性,性能的提高和代码的组织管理
  • 2)大量的样式,覆盖、权重和很多!important, 分好层可以让团队命名统一规范,方便维护
  • 3)有责任感地去命名你的选择器。

CSS Module (css in js)

它不是将CSS改造成编程语言,它只是加了局部作用域和模块依赖,这恰恰是网页组件最急需的功能。

具体用法,请参考阮老师的 CSS Modules用法教程,这里个人做一些总结。

  • 使用 import style from './style.css'; className={style.title}
  • 默认是局部作用域,全局作用域可以这样写
    • :global(.title) {}
    • 显示的局部作用域也可以这样写 :local(.title){}
  • 通过webpack配置可以定制hash
    • loader="localIdentName=[path][name]---[local]---[hash:base64:5]}"
    • .title --> .App---title---GpMto
  • 组合: 一个选择器继承另一个选择器
    • compose: otherClassName from './other.css';
  • 定义变量
    • colors.css @value blue: #0c77f8; @value red: #f20000;
    • app.css
      • @value blue, red from './color.css';
      • color:red; background-color:blue;

BEM

官网

基本规则

BEM的命名规矩很容易记:block-name__element-name--modifier-name,也就是模块名 + 元素名 + 修饰器名。 比如分页组件:

html
<div class="page-btn"> <button type="button" class="page-btn__prev">上一页</button> <!-- ... --> <button type="button" class="page-btn__next">下一页</button> </div>
  • 那么该组件模块就名为page-btn,组件内部的元素命名都必须加上模块名
  • 用双下划线来明确区分模块名和元素名
  • 用双中划线连接修饰器

错误的BEM命名 page-btn__list__item__link

  • BEM的命名中只包含三个部分,如不能出现多个元素名的情况
  • BEM是不考虑结构的(无论父元素名发生改变,或是模块构造发生的改变,还是元素之间层级关系互相变动,这些都不会影响元素的名字。)
  • 这种方式的优点: DOM构造发生变动,至多也就不同元素的增删减,模块内名称也随之增删减,而不会出现修改名字的情况

BEM命名好长

为啥不用子代选择器来代替BEM命名?实际上BEM禁止使用子代选择器,官方给出以下理由

  • 虽然长,但是IDE会自动提示
  • 避免命名冲突(使用子选择器,如果层次关系过长会导致逻辑不清晰,甚至命名冲突)
  • 方便样式覆盖 (同样如果使用子选择器,层级较深覆盖较困难)

修饰器

css
/* 缩小版分页组件中,具体页码按钮隐去 */ .page-btn--min .page-btn__btn { display: none; } .page-btn--min .page-btn__prev { width: 50%; } .page-btn--min .page-btn__prev { width: 50%; }
  • 一般情况下:BEM中修饰器的样式不依赖于任何结构关系
    • 也就是说,元素的状态改变只会影响自身,不能影响其他元素
  • 但实际上,这很难做到的,如果副作用(命名冲突与样式覆盖权重的影响)没那么大,以上的写法也ok
  • JS控制状态的场景:可以使用 如is-activejs-active之类的类名,只用作标识,不予许有默认的公共样式。

原子类和BEM

BEM可以不需要用到原子类,但是如果已经引入了类似Bootstrap的框架,也没必要强制避免使用原子类,比如pull-rightellipsisclearfix等等类,这些类非常实用,和BEM是可以互补的。

在组件开发中其实不推荐使用原子类,因为这会降低组件的可复用性。 可复用性的最理想状态就是组件不仅仅在不同的页面中表现一致,在跨项目的情况下,也能够运行良好。 如果组件的样式因为依赖于某几个原子类就要依赖整个Bootstrap库,那么组件的迁移负担就重很多了。

原子类更适合应用在实际页面中,这是因为页面变动大而且不可复用,假设在header中,我们用到了两个组件logo和user-panel(用户操作面板),两个组件分别置于header的左侧和右侧,我们可以这么写:

html
<div class="header clearfix"> <div class="logo pull-left"> <!-- ... --> </div> <div class="user-panel pull-left"> <!-- ... --> </div> </div>

header可以封装成一个模块,但它复用程度不高,不能算是组件,所以即使使用原子类也没有关系。在项目中,使用原子类之前应该考虑一下,这个场景是否变动大而且不可复用,如果是的话,我们可以放心的使用原子类。 组件应该是“自洽的”,其本身就应该构成了一个“生态圈”,也就是说,他几乎不需要外部供给,自给自足就能够运转下去。

实际页面中也应该使用BEM来避免样式冲突和覆盖

实际应用中如何解决BEM命令过长

  • css-loader placeholder
  • 预处理器如scss less, 模版css通过方法生成

应用方案

样式格式化

为了保证各个浏览器上样式表现一致,有以下方案:

  • reset.css 样式格式化,
  • normalize.css 通过样式补充,保证各浏览器样式体验一致, 比reset.css友好一些
  • neat.css normalize.css的基础上做了些优化
    • http://thx.github.io/cube/doc/neat
    • 解决了一些Bug,特别是低级浏览器的常见Bug。reset.css做了,normalize.css没做
    • 统一效果,但不盲目追求重置为0。reset.css全部重置为0
    • 向后兼容
    • 考虑响应式
    • 考虑移动设备

移动端rem等比缩放方案

  • 由于移动端机型屏幕尺寸很多,为提高效率,大多数移动端的网站或app内嵌页面都是使用rem等比缩放方案,使得在不同屏幕大小的手机上布局一致
  • 原理是根据屏幕宽度设置html元素字体大小,屏幕宽的手机看到的字体会大一些,但一行能容纳的文字数量是一样的

image

核心代码如下

js
const style = document.head.appendChild(document.createElement("style")); window.addEventListener("resize", setRootFontSize); if (navigator.userAgent.indexOf("Android") > -1) { window.addEventListener("DOMContentLoaded", setRootFontSize) } setRootFontSize(); function setRootFontSize() { const size = Math.max(320, Math.min(960, document.documentElement.clientWidth)) / 16; style.innerHTML = "html{font-size: " + size + "px !important;}"; }
  • 等比缩放 除了rem外, 还可以用 vw vh实现

具体案例点这里

移动端0.5px边框

为什么会有1px问题?

先来了解几个概念

  • px单位 它是pixel的缩写 即像素单位,与屏幕的实际宽高对应。
  • window.devicePixelRatio 窗口设备像素比
  • 物理像素 由屏幕的实际宽高就决定
    • 比如 iphone 6手机的宽度是375px高度是667px
  • 逻辑像素DIP 表示屏幕能够展示物体的视觉尺寸知多少
    • 比如 iphone 6手机的屏幕物理宽度是375px 但实际宽度是 1px的距离上能放置2个像素,那么这块屏幕的逻辑像素是 750px

为什么会出现物理像素和物理像素两个概念?

很久以前(pc互联网时代),

  • 主流的显示器 23寸 - 19201080px - 72像素每英寸。也就是整个屏幕能显示 19201080*72 个点阵
  • 从iPhone 4s 开始,乔布斯搞了个 retina 屏幕,显示器的分辨率,是 144像素每英寸。
  • image.png
  • 假设还是之前的23寸显示器,这时候整个屏幕能显示1920108072个点阵
  • 这样的原来一张图在Retina屏幕上只有72/144 = 1/2宽度和高度,这样的话导致老版本应用在Retina屏幕只用了一版的屏幕空间,这不就出bug了吗?
  • 为了解决上述问题所以引入了 物理像素、逻辑像素和DPR的概念
    • 物理像素:不变, 都是23寸
    • 逻辑像素:老显示器是1920,则Retina屏幕显示器是1920*(72/144)
    • 窗口像素比DPR:老显示器为1, Retina显示器为2
    • 有了窗口像素比后, **同一张图片在不同的显示器上以逻辑像素*DPR展示,**这样这张图片新老23寸显示器上就是一样的大小了。
  • 那么屏幕像素比对前端开发来说有什么影响呢?
    border: 0.5px solid red;为例,在iphone6显示器(DPR=2)以及其他较新的显示器(DPR=2,3) 可以正常显示,在老版本显示器(DPR=1)则就不显示边框了。 最早解决1px问题还有一种方案,根据屏幕像素比检测,如果是DPR=1则用 border-width:1px;,反之border-width: 0.5px;
css
div { border: 1px solid #000; } @media (-webkit-min-device-pixel-ratio: 2) { div { border: .5px solid #000; } }

transfrom缩放

  • 优点:支持圆角,上下左右边框随意组合
  • 缺点: width:100%有什么缺点(可能溢出父容器)它就有什么缺点
less
@mixin border1px($color: #eeeeee, $radius: 0, $style: solid, $zIndex: 1) { position: relative; &::before { content: ''; position: absolute; top: 0; left: 0; width: 200%; height: 200%; border: 1px $style $color; border-radius: $radius; transform-origin: 0 0; transform: scale(0.5, 0.5); box-sizing: border-box; pointer-events: none; z-index: $zIndex; @content; } }

背景渐变

scss
/** 0.5px 精美边框 函数 $directions 是一个数组 (T, B, R, L) T代表top, B代表bottom, R代表right, L代表left 循序无关 $borderColor 颜色值 例子: @include myBorders((T, B)); */ @mixin myBorders($directions, $borderColor: #eeeeee, $bg-color: #ffffff) { $directionsMap: ( L: linear-gradient(90deg, $borderColor, $borderColor 50%, transparent 50%) bottom left no-repeat, R: linear-gradient(270deg, $borderColor, $borderColor 50%, transparent 50%) bottom right no-repeat, T: linear-gradient(180deg, $borderColor, $borderColor 50%, transparent 50%) top left no-repeat, B: linear-gradient(360deg, $borderColor, $borderColor 50%, transparent 50%) bottom left no-repeat ); $sizesMap: ( L: 1px 100%, R: 1px 100%, T: 100% 1px, B: 100% 1px ); $dirResult: (); $sizResult: (); @each $direction in $directions { $dirResult: append($dirResult, map_get($directionsMap, $direction), comma); $sizResult: append($sizResult, map_get($sizesMap, $direction), comma); } background: $dirResult; background-size: $sizResult; background-color: $bg-color; }

svg

css
.px { background: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'><line x1='0' y1='100%' x2='100%' y2='100%' stroke='red' /></svg>"); }

除此之外 1px还有一些其他解决方案 参考这里《7种方法解决移动端Retina屏幕1px边框问题》

响应式布局

参考 bootstrap 使用媒体查询

css
.dropdown-menu-left { right: auto; left: 0; } @media (min-width: 576px) { .dropdown-menu-sm-left { right: auto; left: 0; } } @media (min-width: 768px) { .dropdown-menu-md-left { right: auto; left: 0; } } @media (min-width: 992px) { .dropdown-menu-lg-left { right: auto; left: 0; } } @media (min-width: 1200px) { .dropdown-menu-xl-left { right: auto; left: 0; } }

水印

image.png 核心代码如下

js
const size = 255; const canvas = document.createElement('canvas'); canvas.setAttribute('width', `${size}px`); canvas.setAttribute('height', `${size}px`); const draw = canvas.getContext('2d'); draw.textAlign = 'center'; draw.textBaseline = 'middle'; draw.font = '14px Microsoft Yahei'; draw.fillStyle = 'rgba(25,25,25, 0.1)'; draw.rotate(Math.PI / 180 * -15); draw.fillText('youremail@company.com', size/2, size/2); const ele = document.createElement('div'); ele.setAttribute('style', ` position: fixed;top:0;right:0;left:0;bottom:0;z-index:1000;pointer-events:none; background-image: url(${canvas.toDataURL()});background-repeat: repeat; `); document.body.appendChild(ele);

具体案例

Css特效

checkbox特效

image

  • 利用<label for="checkboxId">关联 <input type="checkbox" id="checkboxId">
  • 利用 层次通用选择器 控制div的显示和隐藏

核心代码

html
<style> .content { display: none; } #checkboxId:checked ~ .content { display: block; } </style> <div> <input type="checkbox" class="checkbox" id="checkboxId"> <label for="checkboxId">点我试试看</label> <div class="content"> 如果checkbox:checked 这段内容会显示 </div> </div>

具体案例

由此类推,checkbox还可以实现以下功能

  • 登陆注册切换 tab切换 动画效果也没问题
  • 表单校验
  • element-ui switch组件
  • 其他。。。 替代js控制显示隐藏的

image.png

border特效

border可以做三角形 梯形,波浪线 网格

image.png

具体案例

background特效

image.png

clip-path 背景图片裁剪

  • 配合Svg有神奇的效果

image.png

具体案例

3D变换

image.png

具体案例

transform 变换

image.png

滚动条特效-阅读进度条

image.png

具体案例

其他案例

动画

简单来说CSS动画只有两种

  • animations + keyFrame 关键帧(逐帧)动画

    • 可以循环
  • transfrom变换动画 和 transition 渐变动画

    • 不支持循环但可以监听transitionend 事件实现循环
  • 具体动画的学习可以参考这篇文章《CSS实现动画的几中方式》

  • 这里给一些案例

雪碧图动画

image.png

keyFrames逐帧动画

image.png

具体案例

为什么CSS动画比JS动画性能好

浏览器渲染第二帧及以后帧的过程

  • 重排(也叫回流) --》 重绘 --》合成
  • 重排、重绘是在主线程进行的,JS执行与DOM解析渲染互斥, JS动画可能会出现卡顿情况
  • CSS动画是在合成线程中进行的, 不阻塞主线程,因此即使主线程卡顿了CSS线程还能继续执行,这也是为什么CSS动画比JS动画块

本文作者:郭敬文

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!