|  | 
|  | 1 | +--- | 
|  | 2 | +title: tytemd 中如何实现图片懒加载 | 
|  | 3 | +date: 2024-12-21T11:47:04Z | 
|  | 4 | +slug: post-35 | 
|  | 5 | +author: chaseFunny:https://github.com/chaseFunny | 
|  | 6 | +tags: ["AI","React","前端开发"] | 
|  | 7 | +--- | 
|  | 8 | + | 
|  | 9 | +大家好,我是 luckySnail ,周五正当我和 AI 如胶似漆的时候,后端同事突然和我说有一个地方需要我优化一下,我就问他,什么快和我说,他打开一个项目页面,然后打开控制台和我说,你看,这个网页刚进入就把所有图片都请求了了一次,并且因为请求太多图片导致触发了我们设置的最大限制,导致后面的图片直接请求失败,从而导致左侧图片都是加载失败的样子 | 
|  | 10 | + | 
|  | 11 | + | 
|  | 12 | + | 
|  | 13 | +好家伙,这都是老板白花花的银子呀!赶紧修复上线,可是怎么修复呢? | 
|  | 14 | + | 
|  | 15 | +## 问题分析 | 
|  | 16 | + | 
|  | 17 | +解决问题前,我们可以先分析一下问题,就像我们看病一样,我们总得先挂号,让医生看看身体是怎么了。说回正题,问题分析: | 
|  | 18 | + | 
|  | 19 | +- 页面首次加载时就请求所有图片资源 | 
|  | 20 | +- 大量图片并发请求触发了服务器限制 | 
|  | 21 | +- 导致部分图片加载失败 | 
|  | 22 | +- 造成不必要的带宽消耗和性能浪费 | 
|  | 23 | + | 
|  | 24 | +知道了问题,我们看下解决方案:使用图片懒加载(Lazy Loading)技术。图片懒加载是一种网页性能优化技术,它可以延迟加载页面中不可见的图片,直到用户滚动到可见区域时才进行加载 | 
|  | 25 | + | 
|  | 26 | +其实大厂们也都是这么做的,不信我们看看掘金平台 | 
|  | 27 | + | 
|  | 28 | + | 
|  | 29 | + | 
|  | 30 | + | 
|  | 31 | + | 
|  | 32 | +我们可以看到当我们页面滚动到下面才加载对应的图片 | 
|  | 33 | + | 
|  | 34 | +## 解决问题 | 
|  | 35 | + | 
|  | 36 | +下面我们来解决一下我们教程中图片加载问题,由于我们项目使用 bytemd 作为内容渲染器,所以图片的渲染其实在 bytemd 内部完成的,那我们想要自定义渲染图片的逻辑,也就需要借助 bytemd 的自定义插件的能力了,通过查阅源码得知:插件也就是一个函数,用于扩展 Bytemd 编辑器和查看器的功能,返回指定的对象类型,返回对象类型已经定义好了,是 BytemdPlugin 。它包含五个属性: | 
|  | 37 | + | 
|  | 38 | +- remark:自定义 Markdown 解析 | 
|  | 39 | +- rehype:HTML 解析 | 
|  | 40 | +- actions:注册操作,也就是定义我们编辑框上面哪些小图标的 | 
|  | 41 | +- editorEffect :编辑器副作用 | 
|  | 42 | +- viewerEffect:查看器副作用 | 
|  | 43 | + | 
|  | 44 | +我们需要的是 viewerEffect ,然后我们只需要先获取到所有图片,然后为图片添加上懒加载的逻辑,具体的思路: | 
|  | 45 | + | 
|  | 46 | +1. 在 viewerEffect 中,拿到 markdownBody ,也就是 md 的内容,是一个 DOM 元素 | 
|  | 47 | +2. 通过 querySelectorAll 获取所有 img 标签,然后,我们有两个选择, | 
|  | 48 | +3. 第一,使用游览器自带的图片懒加载 | 
|  | 49 | +4. 第二,使用 IntersectionObserver 来实现懒加载,这种方式可以设置一个占位图,这样体验更加好 | 
|  | 50 | + | 
|  | 51 | +最后,我们看一下具体代码: | 
|  | 52 | + | 
|  | 53 | +```ts | 
|  | 54 | +import type { BytemdPlugin } from 'bytemd' | 
|  | 55 | + | 
|  | 56 | +export interface ImageLazyLoadOptions { | 
|  | 57 | +  // 是否使用原生懒加载 | 
|  | 58 | +  useNativeLazy?: boolean; | 
|  | 59 | +  // 自定义加载占位图 | 
|  | 60 | +  placeholderSrc?: string; | 
|  | 61 | +  // 自定义类名 | 
|  | 62 | +  className?: string; | 
|  | 63 | +} | 
|  | 64 | + | 
|  | 65 | +export default function imageLazyLoad(options: ImageLazyLoadOptions = {}): BytemdPlugin { | 
|  | 66 | +  const { | 
|  | 67 | +    useNativeLazy = true, | 
|  | 68 | +    placeholderSrc = '', | 
|  | 69 | +    className = '' | 
|  | 70 | +  } = options; | 
|  | 71 | + | 
|  | 72 | +  return { | 
|  | 73 | +    viewerEffect({ markdownBody }) { | 
|  | 74 | +      // 获取所有图片元素 | 
|  | 75 | +      const images = markdownBody.querySelectorAll('img'); | 
|  | 76 | + | 
|  | 77 | +      images.forEach((img) => { | 
|  | 78 | +        // 保存原始src | 
|  | 79 | +        const originalSrc = img.getAttribute('src'); | 
|  | 80 | + | 
|  | 81 | +        if (useNativeLazy) { | 
|  | 82 | +          // 使用原生懒加载 | 
|  | 83 | +          img.setAttribute('loading', 'lazy'); | 
|  | 84 | +        } else { | 
|  | 85 | +          // 使用 Intersection Observer 实现懒加载 | 
|  | 86 | +          const observer = new IntersectionObserver((entries) => { | 
|  | 87 | +            entries.forEach((entry) => { | 
|  | 88 | +              if (entry.isIntersecting) { | 
|  | 89 | +                const obImg = entry.target as HTMLImageElement; | 
|  | 90 | +                if (originalSrc) { | 
|  | 91 | +                  obImg.src = originalSrc; | 
|  | 92 | +                } | 
|  | 93 | +                observer.unobserve(obImg); | 
|  | 94 | +              } | 
|  | 95 | +            }); | 
|  | 96 | +          }); | 
|  | 97 | + | 
|  | 98 | +          // 设置占位图 | 
|  | 99 | +          if (placeholderSrc) { | 
|  | 100 | +            img.src = placeholderSrc; | 
|  | 101 | +          } | 
|  | 102 | + | 
|  | 103 | +          // 将原始图片地址存储在data属性中 | 
|  | 104 | +          img.dataset.src = originalSrc || ''; | 
|  | 105 | +          img.src = placeholderSrc; | 
|  | 106 | + | 
|  | 107 | +          // 添加自定义类名 | 
|  | 108 | +          if (className) { | 
|  | 109 | +            img.classList.add(className); | 
|  | 110 | +          } | 
|  | 111 | + | 
|  | 112 | +          // 开始观察 | 
|  | 113 | +          observer.observe(img); | 
|  | 114 | +        } | 
|  | 115 | +      }); | 
|  | 116 | +    } | 
|  | 117 | +  } | 
|  | 118 | +} | 
|  | 119 | +``` | 
|  | 120 | + | 
|  | 121 | +我们看看效果吧! | 
|  | 122 | + | 
|  | 123 | + | 
|  | 124 | + | 
|  | 125 | + | 
|  | 126 | + | 
|  | 127 | + | 
|  | 128 | + | 
|  | 129 | +完美修复bug,也节省了开销 | 
|  | 130 | + | 
|  | 131 | +## 开源 tytemd 的图片懒加载插件 | 
|  | 132 | + | 
|  | 133 | +相信肯定也有其他小伙伴有同样的问题,那就开源吧,毕竟谁会拒绝直接拿来使用呢? | 
|  | 134 | + | 
|  | 135 | +地址:https://www.npmjs.com/package/bytemd-plugin-image-lazy | 
|  | 136 | + | 
|  | 137 | +如果你觉得写得不错,对你有帮助,点个赞再走吧! | 
|  | 138 | + | 
|  | 139 | +哦对了,有人好奇如何从 0 发布一个 npm 包吗? | 
|  | 140 | + | 
|  | 141 | +--- | 
|  | 142 | +此文自动发布于:<a href="https://github.com/coderPerseus/blog/issues/35" target="_blank">github issues</a> | 
0 commit comments