原稿:语雀 · CSS 题目 · 原目录:面向面经学习 › CSS 题目

绝对定位元素塌陷

三个盒子自外而内嵌套,position属性依次为,fixed, absolute,relative(顺序可能不一样,记不太清了)。如果给最里面的盒子添加padding,问三个盒子的高度会如何变化

.fixed-box {
  position: fixed;
  margin: 0 auto;
  background-color: hotpink;
}
.absolute-box{
  position: absolute;
  width: 120px;
  background-color: indianred;
}
.relative-box{
  position: relative;
  width: 100px;
  height: 80px;
  background-color: khaki;
  padding: 10px;
}

经过实测,这道题中,relative-box 的 padding 修改时会影响 absolute-box 的高度,而最外层 fixed-box 若不设置高度宽度,则始终保持 0 0

原因:W3 10.6.7 给出说明:对于 BFC 来说

  • 绝对定位的子元素会被忽略,无法撑起高度
  • float 元素可以撑起其高度

因此最外层盒子的高度不会改变,而绝对定位盒子的高度会随相对定位盒子高度改变


滑动按钮

实现点击右滑动按钮,同时要求 button 自适应父元素宽度

  • 滑动使用 transition 即可
  • 自适应,可以为 button 设置 width:100%,因为 button 是行内块元素,宽度有效,或者使用 flex 布局
.container {
  width: 150px;
}
.btn {
  width: 100%;
  /* transition:属性名 持续事件 时间函数 推迟变化时间 */
  transition: margin-left 1s;
}
.btn:hover {
  margin-left: 50px;
}

递归判断 body 结构是否相同

let body1 = document1.body;
let body2 = document2.body;

const compareDOM = function (elem1, elem2) {
  let [children1, children2] = [elem1.children, elem2.children];
  if (children1.length != children2.length) return false;
  for (let i=0; i<children1.length; i++) {
    if (!compareDOM(children1[i], children2[i])) return false;
  }
  if (elem1.tagName != elem2.tagName || elem1.innerText != elem1.innerText)
    return false;
  return true;
};

console.log(compareDOM(body1, body2));

借助 Element.children() 可获取当前元素的子元素列表(动态的 HTMLCollection),递归的获取子元素即可,到达最底层子节点后比较节点的 tagName 和 innerText,然后返回至上一层——先序遍历


负 maring 意义

  • 元素自身无宽度,设置负值水平 maring:增加元素宽度。当设置 margin-left:-100px 元素会在填充完父元素宽度后向左溢出 100px 宽度
image.png
- 元素设置了宽度,设置负值水平 maring:产生位移(圣杯布局) - 元素设置负值 margin-top:与自身高度无关,会导致元素向上偏移(垂直居中) - 元素设置负值 margin-bottom:与自身高度无关,也不会偏移,会让元素 CSS 作用的范围减小,如背景色;可比较 inline-block 元素垂直方向的 padding 可以让 CSS 作用范围扩大,但不会增加元素实际尺寸

如何减少回流重绘

  1. 使用 transform 实现样式效果:transform 基于合成过程重新生成,不涉及主线程
  2. 使用 display:none 隐藏元素避免回流重绘、使用 visiablity:hidden 只是避免回流
  3. 当要有大量新增样式的操作时,可以使用 document fragment 将最终更新的结果插入,这样只触发一次回流重绘
  4. 使用 rAF 调节渲染:很多情况下频繁的重新渲染无法避免,比如页面滚动与动画,这些场景下通常要保证每秒 60帧的渲染速度,一帧的生成开始于系统的垂直同步信号->处理 IO,调用处理程序-> 执行 rAF() 回调 -> 重新渲染。如果将所有动画函数放在 rAF 回调中,那它就会被放在下一帧渲染前执行,这样将动画效果集中在一次回流重绘中就能渲染,避免了 DOM 修改时再额外导致回流重绘。

动画:淡出-平移-放大

一个三角形从透明到实体,持续一秒,然后匀速前进五秒,然后变大持续两秒

      .triangle {        width: 0px;        animation: change 8s linear 1 forwards;      }      @keyframes change {        0% {          transform: translateX(0) scale(1);          border-left: 50px solid transparent;          border-right: 50px solid transparent;          border-bottom: 85px solid transparent;        }        12.5% {          transform: translateX(0) scale(1);          border-left: 50px solid transparent;          border-right: 50px solid transparent;          border-bottom: 85px solid orange;        }        75% {          transform: translateX(300px) scale(1);          border-left: 50px solid transparent;          border-right: 50px solid transparent;          border-bottom: 85px solid orange;        }        100%{          transform: translateX(300px) scale(2);          border-left: 50px solid transparent;          border-right: 50px solid transparent;          border-bottom: 85px solid orange;        }      }    </style>
  • forwards 表示动画结束时保持结尾帧
  • 注意每一个关键帧中要声明不变的属性,假如只在 100% 中声明  scale(2),这个放大会开始于 0%

动画:遮罩 + 弹窗

写css、HTML,完成一个弹窗,弹窗自底部往上弹出,弹窗垂直水平居中,长宽视内容的长宽而定,外部为灰色蒙版

popup.gif
<style>
  .cover {
    position: fixed;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    background-color: rgb(128, 128, 128, 0.7);
    z-index: 0;
  }
  .popup {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    background-color: tomato;
    padding: 50px 80px;
    z-index: 1;
    animation: goAbove 0.5s;
  }

  @keyframes goAbove {
    0% {margin-top: 100%;}
    100% {margin-top: 0;}
  }
</style>
<div>被遮罩掩盖</div>

<div class="popup">这是一个弹窗</div>
  • 遮罩的实现需要靠 fixed 布局,指定 left top 为 0,声明颜色时需要使用 rgb(),第四个代表透明度

往 ul 里面插入10000个 li

let list = document.getElementById("list");
let frag = document.createDocumentFragment();
for(let i=0; i<10000; i++){
  let li = document.createElement("li");
  li.innerText = i;
  frag.appendChild(li);
}
list.appendChild(frag);

使用 document.createDocumentFragment() API,创建一个单独的文档对象(一个微型 document),先向该对象上插入 10000 个 <li>,之后将该片段插入 <ul>,其中的 10000 个节点被一次性插入,只触发一次重新渲染过程;如果每次都直接向文档中插入 <li>,就会触发 10000 次重新渲染


判断元素是否在可视区

参考链接:如何判断元素是否进入可视区域viewport?

CSS 题目 配图

方法一:

el.offsetTop - document.documentElement.scrollTop <= document.documentElement.clientHeight

offsetTop 是元素盒子边缘距离其 offsetParent padding 的距离,scrollTop 是元素被卷起来的顶部距离当前视口顶部的距离,当 offsetParent 就是 body 时,二者差值就是元素距离当前视口顶端的距离,当这个差值大于 body 的 clientHeight(元素非滚动区域的 padding+content),说明元素还在可视区下方

CSS 题目 配图
function checkInView(el) {
    // viewPortHeight 兼容所有浏览器写法
    const viewPortHeight = document.documentElement.clientHeight
    const offsetTop = el.offsetTop
    const scrollTop = document.documentElement.scrollTop
    const top = offsetTop - scrollTop
    return top <= viewPortHeight
}

方法二:

判断 elem.getBoundingClientRect().top >= top 说明未进入可视区,一般认为 gBCR right 和 left 的差值与 offsetWidth 的值相等(content+padding+border+scrollY),而 bottom 和 top 的差值与 offsetHeight 相等(content+padding+border+scrollX)

window.addEventListener("scroll", () => {
  checkInView();
});

const checkInView = function () {
  const children = Array.from(document.getElementById("list").children);
  children.forEach((elem) => {
    const viewportHeight = document.documentElement.clientHeight;
    const top = elem.getBoundingClientRect().top;
    if (top <= viewportHeight) console.log(`${elem.id} is in view`);
    else console.log(`${elem.id} is out of view`);
  });
};

效果可以见:https://jsfiddle.net/nepjnq/ouwLmfpq/2/