fengyuanchen / viewerjs

JavaScript image viewer.
https://fengyuanchen.github.io/viewerjs/
MIT License
7.85k stars 1.24k forks source link

显示1000张图片合集时的性能优化建议和BUG #547

Open xiaosongmao123 opened 2 years ago

xiaosongmao123 commented 2 years ago

很好的项目,感谢你的工作

Describe the bug 当我想要用viewerjs去显示图片合集时(无法预估数量,所以要尝试最大值1000张)。发现性能很差。

然后就分析了一下,发现,性能差完全是viewerjs设计问题+BUG,而不是我的需求--显示1000张图片不对。

To Reproduce Steps to reproduce the behavior: 1.创建1000张图片的合集

 <ul id="images" style={{ display: 'none' }}>

                  {showlist.map((item) => (
                    <li key={item.fileindex}>

                      <img src={item.src} data-src={item.datasource} referrerPolicy="no-referrer" />

                    </li>
                  ))}

</ul>

2.初始化viewjs

this.viewver = new Viewer(document.getElementById('images')!, {...})

3.点击幻灯片播放

Expected behavior 预期正常展现。实际很差,很卡

Additional context

我发现的问题比较多,我没有按照重要程度去排序,所以请耐心看一下。都是为了让Viewer更完美


问题1: viewerjs强制要求必须用img列表去实例化new Viewer(),带来了不必要的消耗

在我的用例中,ul>li>img (1000张),它实际上并没有实际意义。因为Viewer在实例化时,是Viewer自己重新创建了1000个新的img并复制原始数据

//https://github.com/fengyuanchen/viewerjs/blob/main/src/js/render.js  line 82
forEach(this.images, (image, index) => {
      const { src } = image;
      const alt = image.alt || getImageNameFromURL(src);
      const url = this.getImageURL(image);

      if (src || url) {
        const item = document.createElement('li');
        const img = document.createElement('img');

        forEach(options.inheritedAttributes, (name) => {
          const value = image.getAttribute(name);

          if (value !== null) {
            img.setAttribute(name, value);
          }
        });

        img.src = src || url;
        img.alt = alt;

如上,那么最开始创建的ul>li>img (1000张),实际上完全可以创建为 ul>li>span (1000个) 来代替。因为Viewer只是需要读取复制数据,在span上设置这些数据给Viewer读取也是一样的<span src={item.src} data-src={item.datasource} referrerPolicy="no-referrer" />。这样我最开始不需要创建和加载1000个img (导致网络卡)

这个问题,我自己使用替代图片的方式优化了一下(使用一张很小的固定图片替换了img 的 src)(在Viewer的view: (e: any)事件中还有处理替换回来)


 <ul id="images" style={{ display: 'none' }}>
                  {showlist.map((item) => (
                    <li key={item.fileindex}>

                       //图片占位符替换
                      <img src="data:image/png;base64,iVBORw0..." data-imgsrc={item.src}  data-src={item.datasource} referrerPolicy="no-referrer" />

                    </li>
                  ))}
</ul>

问题2: Viewer一次性创建了1000个新的img,导致第一次展现时,需要加载1000张图片,网络导致很卡

还是如上//https://github.com/fengyuanchen/viewerjs/blob/main/src/js/render.js line 82 Viewer一次性创建了1000个新的img,这些img是被用来放在viewer-navbar做导航用的

//Viewer最终渲染的html

<div class="viewer-container">
<div class="viewer-navbar">
<ul class="viewer-list viewer-transition" role="navigation" style="width: 30906px; transform: translateX(307.5px);">

<li data-index="0" data-viewer-action="view" role="button" tabindex="0" class="">
//1000张小图片
<img  src="...” />
</li>
......
</ul>
</div>
</div>

创建1000个?感觉不合理,因为导航条放不下1000个,应该先创建一部分,当切换下一张图片,导航接近时,再补充、替换一部分。这样循环

或者,创建1000个img,但是不要复制真实的src,而是在导航接近时,更新需要显示的那些img的src,让导航图片正常显示

针对这个问题,我是在Viewer的view: (e: any)事件中还有处理替换回来。这样,仅保持几十个img有真实的src,其他的只是占位。可以避免加载1000张图片带来的网络卡顿


viewver = new Viewer(document.getElementById('images')!, {           
            url: 'data-src',
            view: (e: any) => {
              const nextindex = e.detail.index /** 马上要显示的图片的id */
              let imagelist = rawimagelist.value
              let rawimg = imagelist[nextindex]

              let ul = document.getElementsByClassName('viewer-list viewer-transition')
              if (ul && ul.length > 0) {
                let lilist = ul[0].childNodes
                /** 找到当前显示的图片,更新前后30个li>img 的src */
                for (let i = Math.max(0, nextindex - 30), maxi = Math.min(nextindex + 30, imagelist.length); i < maxi; i++) {
                  let imgnode = lilist[i].firstChild
                  if (imgnode) {
                    let img = imgnode as Element
                    let src = img.getAttribute('src')
                    let smallurl = imagelist[i].smallurl
                    if (src != smallurl) img.setAttribute('src', smallurl)
                    src = img.getAttribute('data-original-url')
                    let bigurl = imagelist[i].bigurl
                    if (src != bigurl) img.setAttribute('data-original-url', bigurl)
                  }
                }
              }
            }
          })
        }

问题3: 点击幻灯片播放时会卡住

原因是,点击播放时,Viewer会创建1000个大图的img

这里有个BUG了,因为这1000张大图是放在viewer-player里播放的,但我竟然在body下还有1000个,这应该是BUG了

<body>
    <div class="viewer-container">
        <div class="viewer-player viewer-show" >
            <img .../>
            ....1000...//应该是放在这里,循环播放的
        </div>
</div>
<img .../>
....1000...//但是在body下竟然还有1000张?
</body>

针对这个问题,还是那个思路,当播放时,播放到第几张图片,就同时替换更新邻近的几张图片的src。而不是一点击播放按钮就创建1000张图片并发起1000个网络请求


总结,Viewer没有对大量图片的情景进行优化。问题3可能是BUG,我没时间翻Viewer的代码仔细看了

请不要回复一句:这可能需要修改Viewer的架构了。。。。

其实并不是这样的,最简易的就是使用占位图片。先占位。然后等临近显示时更新替换为正确的图片。这样即使创建1000个img,也不会真的立即联网加载1000张图片,导致网络太卡。

当然最合理的方式,还是修改一下显示逻辑,仅创建几十个必要的img,然后等临近显示时更新这几十个img的图片地址。这样不需要创建1000个img(现在实际上是创建了3000个img,并且立即一次性加载1000张图片).

fengyuanchen commented 2 years ago

目前 v1 版本暂时这样了。如果后面有时间做 v2 版本,可以重构一下。

ZilinCui commented 2 months ago

请问源代码中向body中插入创建的图片都作用是什么 // iOS Safari will convert the image automatically // with its orientation once append it into DOM if (!IS_SAFARI) { newImage.style.cssText = ( 'left:0;'