浏览器 渲染机制:重绘&重排

146 阅读3分钟

重排

定义

  • 导致浏览器不得不重新计算元素的几何属性,并重新构建渲染树

  • 负责元素的几何属性更新

  • DOM结构中的各个元素都有自己的盒子(模型),这些都需要浏览器根据各种样式来计算并根据计算结果将元素放到它该出现的位置,这个过程称之为reflow

触发Reflow

  • 增加、删除、修改DOM节点时,会导致Reflow或Repaint

  • 移动DOM的位置,动画

  • 修改CSS样式

  • Resize窗口的时候(移动端没有这个问题),或者是滚动的时候

  • 修改网页

触发页面重布局的一些css属性

盒子模型相关属性会触发重布局

  • width / height / min-height

  • padding / margin

  • display

  • border

    • border-width

定位属性及浮动也会触发重布局

  • top / bottom

  • left / right

  • position

  • float

  • clear

改变节点内部文字结构也会触发重布局

  • font

    • font-weight

    • font-family

    • font-size

  • text-align

  • overflow-y

  • overflow

  • line-height

  • vertical-align

  • white-space

重绘

定义

  • 完成重排后,要将重新构建的渲染树渲染到屏幕上

  • 负责元素的样式更新

  • 当各种盒子的位置、大小以及其他属性,例如颜色、字体大小等都确定下来后,浏览器于是便把这些元素都按照各自的特性绘制了一遍,于是页面的内容出现了,这个过程称之为repaint

重排必然带来重绘,但是重绘未必带来重排

触发Repaint

  • DOM改动

  • CSS改动

  • 改变某个元素的背景

只触发重绘不触发重排的一些CSS属性

  • color

  • border-style

    • border-radius
  • visibility

  • text-decoration

  • background

    • background-image

    • background-position

    • background-repeat

    • background-size

  • outline

    • outline-color

    • outline-style

    • outline-width

  • box-shadow

减少重绘和重排的措施包括

  • js 少访问 dom节点和 css 属性

    • 增加,修改,删除元素

    • 先把该dom节点抽离到内存中进行复杂的操作,再display到页面上(虚拟DOM)

  • 减少不必要的 DOM 层级(DOM depth)

    • 改变 DOM 树中的一级会导致所有层级的改变

    • 上至根部,下至被改变节点的子节点

  • 不要通过父级来改变子元素样式

    • 最好直接改变子元素样式

    • 改变子元素样式不要影响父元素和兄弟元素的大小和尺寸

  • 通过class来设计元素样式,切忌用style 多次操作单个属性

  • 为产生动画的 HTML 元素使用 fixedabsoluteposition

    • 修改 CSS 是不会 Reflow 的
  • img标签要设置高宽,以减少重绘重排

  • 把DOM离线后修改,如将一个dom脱离文档流

    • 比如display:none ,再修改属性,这里只发生一次回流
  • 尽量用 transform 来做形变和位移,不会造成 Reflow

  • 权衡速度的平滑

    • 实现一个动画,以1个像素为单位移动这样最平滑,但 reflow就会过于频繁,CPU很快就会被完全占用

    • 如果以3个像素为单位移动就会好很多

  • 不要用tables布局

    • 一个元素触发reflow就会导致table里所有的其它元素reflow

    • 在适合用table的场合,可以设置table-layout为auto或fixed

  • 避免不必要的 CSS 选择器

    • 后代选择器(descendant selectors)

    • 为了匹配选择器将耗费更多的 CPU

display:none vs visibility:hidden

  • display:none会触发reflow

  • visibility:hidden只会触发repaint

    • 没有发现位置变化

场景

  • 一次性改变样式

    var el = document.querySelector('.el');  
    el.style.borderLeft = '1px';  
    el.style.borderRight = '2px';  
    el.style.padding = '5px';  
    
    var el = document.querySelector('.el')
    el.style.cssText = 'border-left: 1px; border-right: 2px; padding: 5px'
    // css
    .active {  
       padding: 5px;
       border-left: 1px;  
       border-right: 2px;  
    }  
    // javascript
    var el = document.querySelector('.el')
    el.className = 'active'
    
  • 隐藏元素,进行修改后,然后再显示该元素

     let ul = document.querySelector('#mylist')
     ul.style.display = 'none'
     appendNode(ul, data)
     ul.style.display = 'block'
    
  • 缓存布局信息

    current = div.offsetLeft
    div.style.left = 1 + ++current + 'px'
    div.style.top = 1 + ++current + 'px'
    
  • 使用文档片段创建一个子树,然后再拷贝到文档中

    let fragment = document.createDocumentFragment()
    appendNode(fragment, data)
    ul.appendChild(fragment)
    
  • 将原始元素拷贝到一个独立的节点中,操作这个节点,然后覆盖原始元素

    let old = document.querySelector('#mylist')
    let clone = old.cloneNode(true)
    appendNode(clone, data)
    old.parentNode.replaceChild(clone, old)