blog-frontend icon indicating copy to clipboard operation
blog-frontend copied to clipboard

ResizeObserver

Open Caaalabash opened this issue 4 years ago • 0 comments

1. 出处

element-ui src/utils/resize-event.js

1.1 完整代码

import ResizeObserver from 'resize-observer-polyfill';

const isServer = typeof window === 'undefined';

/* istanbul ignore next */
const resizeHandler = function(entries) {
  for (let entry of entries) {
    const listeners = entry.target.__resizeListeners__ || [];
    if (listeners.length) {
      listeners.forEach(fn => {
        fn();
      });
    }
  }
};

/* istanbul ignore next */
export const addResizeListener = function(element, fn) {
  if (isServer) return;
  if (!element.__resizeListeners__) {
    element.__resizeListeners__ = [];
    element.__ro__ = new ResizeObserver(resizeHandler);
    element.__ro__.observe(element);
  }
  element.__resizeListeners__.push(fn);
};

/* istanbul ignore next */
export const removeResizeListener = function(element, fn) {
  if (!element || !element.__resizeListeners__) return;
  element.__resizeListeners__.splice(element.__resizeListeners__.indexOf(fn), 1);
  if (!element.__resizeListeners__.length) {
    element.__ro__.disconnect();
  }
};

2. ResizeObserver API

2.1 个人理解

开发过程当中经常遇到的一个问题就是如何监听一个div的尺寸变化?, 常用的做法是监听window.resize事件

这样做有如下缺陷:

  • window.resize事件触发频率高, 这很容易导致性能问题

  • window.resize事件获得每个视窗大小的变化, 不仅仅是某一个元素变化, 这通常是浪费的

  • 只有window才有resize事件

  • window.resize事件不能帮助我们观察到元素地添加或删除

resize gif

ResizeObserver 正好完美的解决了上面的问题, 个人认为它可以完美替换window.resize 它对所观察到的元素的大小的变化做出反应, 具体行为如下

  • 当观察到的元素被插入或从DOM中删除时, 观察将会触发

  • 当观察到的元素displaynone时, 观察将会触发

  • 观察不会由csstransform触发

  • 如果元素有显示,而且元素大小不是0,0,观察将会触发

  • 观察不会对未替换的内联元素(non-replaced inline element)触发

2.2 API

实例化

const myObserver = new ResizeObserver(entries => {

})

用于观察多个元素

myObserver.observe(elementA)
myObserver.observe(elementB)

取消对某个元素的观察

myObserver.unobserve(elementA)

取消对所有元素的观察

myObserver.disconnect()

通过观察, 可以获得一个ResizeObserverEntry对象数组, 该对象有如下属性

{
  target: 触发resize的元素的引用
  contentRect: { x, y, width, height, top, left, bottom, left }
}

getBoundingClientRect不同, contentRect的行为非常怪异, 虽然一般我只需要 width以及height, 行为如下

width = content-box width
height = content-box height
x = left = padding-left
y = top = padding-top
right = width + padding-left
bottom = height + padding-top

3. 应用: 以Vue为例

3.1 假设我需要实时维护一个innerWidth的值

使用window.resize

export default {
  data: () => ({
    innerWidth: '',
  }),
  methods: {
    handleResize() {
      this.innerWidth = window.innerWidth
    },
  },
  mounted() {
    this.innerWidth = window.innerWidth
    window.addEventListener('resize', this.handleResize)
  },
  beforeDestory() {
    window.removeEventListener('resize', this.handleResize)
  },
}

使用ResizeObserver

export default {
  data: () => ({
    resizeObserver: null,
    innerWidth: '',
  }),
  mounted() {
    this.resizeObserver = new ResizeObserver(entries => {
      this.innerWidth = entries[0].contentRect.width
    })
    this.resizeObserver.observe(this.$el)
  },
  beforeDestory() {
    this.resizeObserver.disconnect()
  },
}

差别不是特别大, 因为其当观察到的元素被插入或从DOM中删除时, 观察将会触发的特点, 可以少掉赋初始值的一步

3.2 v-resize指令

import ResizeObserver from 'resize-observer-polyfill'

function inserted(el, binding) {
  if (typeof binding.value !== 'function') return

  const ResizeObserver = new ResizeObserver(binding.value)
  ResizeObserver.observe(el)

  el._ro = ResizeObserver
}
function unbind(el) {
  if (!el._ro) return

  el._ro.disconnect()
  delete el._ro
}

export default {
  inserted,
  unbind,
}

4. 参考

ResizeObserver - 大漠

ResizeObserver - MDN

ResizeObserver - 知乎

polyfill - github

Caaalabash avatar May 02 '20 08:05 Caaalabash