v-lazy自定义指令实现图片懒加载(非IntersectionObserver方案)

看了 ElImage 的源码后,改进了之前用 IntersectionObserver 实现的图片懒加载指令。在我的实现中 IntersectionObserver 的主要问题是不支持 rootMargin,只能在图片进入视口时加载。而 ElImage 的实现方案是判断图片是否进入滚动容器,在这个基础上我们略加配置即可实现 topMargin 预加载的功能。

import {
  useThrottleFn,
  useEventListener
} from '@vueuse/core'

function isScroll(el) {
  let overflow = el.style['overflow'];

  if (!overflow) {
    overflow = document.defaultView.getComputedStyle(el)['overflow'];
  }

  return ['auto', 'scroll', 'overlay'].some((v) => v === overflow)
}

function getScrollContainer(el) {
  let parent = el;

  while (parent) {
    if ([window, document, document.documentElement].includes(parent)) {
      return window;
    }

    if (isScroll(parent)) {
      return parent;
    }

    parent = parent.parentElement;
  }

  return parent;
}

function isInContainer(el, container, marginTop = 0) {
  let rect;
  const {
    top,
    left,
    right,
    bottom
  } = el.getBoundingClientRect();
  if (container instanceof Element) {
    rect = container.getBoundingClientRect();
  } else {
    rect = {
      top: 0,
      left: 0,
      bottom: window.innerHeight,
      right: window.innerWidth
    }
  }

  return (
    top - marginTop < rect.bottom &&
    bottom > rect.top &&
    left < rect.right &&
    right > rect.left
  )
}

export default {
  mounted(el, bindings) {
    const {
      top,
      src
    } = bindings.value;

    const container = getScrollContainer(el);

    const loadImageHandler = () => {
      if (isInContainer(el, container, parseInt(top) || 0)) {
        el.setAttribute('src', src);
        stopListener()
      }
    }

    const stopListener = useEventListener(container, 'scroll', useThrottleFn(loadImageHandler))

    setTimeout(loadImageHandler, 50);
  },
}
<template>
  <div class="thumb" :class="{ video: isVideo(post.file_url) }">
    <img
      src="/src/assets/image/loading.gif"
      :alt="`post. ${post.id}`"
      v-lazy="img"
    />
  </div>
</template>

<script>
export default {
  props: {
    post: {
      type: Object,
    },
  },
  methods: {
    isVideo(url) {
      return url !== "" && (url.endsWith("webm") || url.endsWith("mp4"));
    },
  },
  computed: {
    img() {
      return {
        src: this.post.preview_url,
        top: 250,
      };
    },
  },
};
</script>

<style scoped>
.thumb {
  height: 200px;
  width: 200px;
  cursor: pointer;
  overflow: hidden;
  transition: transform 0.2s ease;
  /* box-shadow: 0 0 5px rgba(0, 0, 0, 0.3); */
}

.thumb:hover {
  transform: translateY(-5px);
}

.thumb.video {
  outline: 2px solid blue;
}

.thumb > img {
  object-fit: contain;
  width: 100%;
  display: block;
  height: 100%;
}
</style>
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值