ElementPlus 源码学习之 el-image 的基本实现思路

el-image 的基本功能有:

  1. 加载中占位
  2. 加载失败占位
  3. 加载成功显示图片
  4. 图片懒加载

先考虑加载占位的情况,我们不难发现加载有成功、失败、加载中这三种状态,由于加载成功这个状态可以视为其他两种状态的排除,所以实际上我们只需要定义两个状态:

// 默认为加载中状态
loading = ref(true);
// 默认加载没有出错
error = ref(false); 

然后提供插槽让用户可以自定义加载中和出错的图片占位:

<template>
  <slot v-if="loading">
    <span>Loadingspan>
  slot> 
  <slot v-else-if="error">
    <span>Errorspan>
  slot>
  <img v-else :src="src"/>
template>

页面结构搭好后,就该考虑如何监听图片加载事件了。el-image 的方法很巧妙,通过新建一个 image 对象并设置这个对象的 src 加载图片,然后监听这个对象的加载事件,通过不同的事件更改状态:

loadImage() {
  const img = new Image();
  // 图片加载成功(但不一定加载完成)
  img.addEventLisntener('load', () => {
    loading = false;
    error = false;
  })
  // 图片加载失败
  img.addEventLisntener('error', () => {
    loading = false;
    error = true;
  })

  // 这里省去了 attrs 绑定到 img 上的代码
  // ...
  
  img.src = src;
}

于是我们可以在 mounted 事件中用 loadImage 方法加载图片(如果不是懒加载的话)。

懒加载
el-image 的懒加载实现方式是判断图片是否进入滚动容器,如果进入就调用 loadImage 方法。这样的好处是无需指定一个具体的 container(和 IntersectionObserver 不同)。那么如何找到滚动容器和判断图片是否进入滚动容器呢?下面提供一个简化版的实现:

// 获取最近的滚动容器
function getScrollContainer(el) {
  let parent = el;
  while(parent) {
    // 如果已经找到顶了还是没找到滚动容器的话,就直接返回 window
    if([window, document, document.documentElement].includes(parent)) {
      return window;
    }
    // isScroll 判断元素是否是滚动元素
    // 原理很简单,就是判断元素的 overflow 是否为 auto、overlay 或者 scroll
	if(isScroll(parent)) {
      return parent;
    }
   
    // 继续往上走
    parent = parent.parentElement;
  }  

  // 通常情况到这里 parent 一定是 null
  return parent;
}

// 判断一个元素是否在另一元素中
// 只要 el 在 container 中可见,就返回 true
// 也就是说这个函数不是判断一个元素是否完全被另一个元素包裹
function isInContainer(el, container) {
  let elRect = el.getBoundingClientRect();
  let rect;
  if(container instanceof Element) {
    rect = container.getBoundingClientRect();
  } else {
    // 如果 container 不是 element,直接设置 rect 为视口 rect
    rect = {
	    top: 0, 
	    left: 0, 
	    right: window.innerWidth, 
	    bottom: window.innerHeight
    }
  }

  return (
    elRect.top < rect.bottom &&
    elRect.bottom > rect.top &&
    elRect.rigth > rect.left &&
    elRect.left < rect.right
  )
}

有了这两个工具函数,我们还需一个注册懒加载的函数:

function lazyLoadHandler() {
  if(isInContainer(container, scrollContainer)) {
    loadImage();
    // 移除事件监听
    scrollContainer.removeEventLisntener('scroll', lazyLoadHandler)
  }
}

function addLazyLoadEventListener() {
  const scrollContainer = getScrollContainer(container);
  // 此处可以有个节流函数
  scrollContainer.addEventListener('scroll', lazyLoadHandler);
}

最终结果:

mounted(() => {
  if(lazyLoad) {
    addLazyLoadEventListener();
    // 100ms 后自动调用一次懒加载判断
    // 避免用户没有触发 scroll 事件导致图片加载不会触发
    setTimeout(() => {
      lazyLoadHandler()
    }, 100);
  } else {
    loadImage()
  }
})

你可能感兴趣的:(Vue,前端,javascript)