如何在html/javascript中加速显示本地文件系统中的大型图像

如何在html/javascript中加速显示本地文件系统中的大型图像,javascript,html,angularjs,typescript,rxjs,Javascript,Html,Angularjs,Typescript,Rxjs,我有一个html应用程序,我正在处理大量的大图像。我们谈论的可能是5000张照片,每张大约3-5MB 到目前为止,我正在测试大约1000张图片,而且速度已经相当慢了 我使用拖放和文件读取器加载图像,然后将文件读取器结果设置为图像源: private loadImageFromDisk(image: IImage): Rx.Observable<IImage> { return Rx.Observable.defer( () => {

我有一个html应用程序,我正在处理大量的大图像。我们谈论的可能是5000张照片,每张大约3-5MB

到目前为止,我正在测试大约1000张图片,而且速度已经相当慢了

我使用拖放和文件读取器加载图像,然后将文件读取器结果设置为图像源:

    private loadImageFromDisk(image: IImage): Rx.Observable<IImage> {

        return Rx.Observable.defer( () => {
            console.log( `loading ${image.file.name} from disc` );
            console.time( `file ${image.file.name} loaded from file system` );

            const reader = new FileReader();
            setTimeout( () => reader.readAsDataURL(image.file), 0 ) ;

            const subject = new Rx.Subject();

            reader.onload = event => {
                subject.onNext( reader.result );
                subject.onCompleted();
            }

            return subject
                .safeApply(
                    this.$rootScope,
                    result => {
                        console.timeEnd( `file ${image.file.name} loaded from file system` );
                        image.content = reader.result;
                    }
                )
                .flatMap( result => Rx.Observable.return( image ) );
        } );
    }
private loadImageFromDisk(图像:IImage):Rx.可观察{
返回接收可观察延迟(()=>{
log(`loading${image.file.name}从光盘`);
time(`file${image.file.name}从文件系统加载);
const reader=new FileReader();
setTimeout(()=>reader.readAsDataURL(image.file),0);
const subject=new Rx.subject();
reader.onload=事件=>{
subject.onNext(reader.result);
subject.onCompleted();
}
返回主题
.safeApply(
这是$rootScope,
结果=>{
console.timeEnd(`file${image.file.name}从文件系统`)加载);
image.content=reader.result;
}
)
.flatMap(结果=>Rx.Observable.return(图像));
} );
}
html:


{{photo.file.name}
{{controller.formatResponse(photo.response)}
我知道ng repeat可能是一个性能问题,我将对此进行排序,但目前即使显示一张图像也可能需要几秒钟。如果我从光盘加载图像,但实际上不显示,则从光盘加载每个图像只需50-100毫秒。如果我显示它,事情会变得慢得多

我怀疑是浏览器(chrome)不得不调整图像的大小导致了速度减慢

在一次测试中,我使用了70幅图片,我将它们全部加载到浏览器中,在加载并渲染完所有图片后,最初几次我在页面上下滚动时,滚动性能变慢,之后页面变得平滑

到2000年,这些图像约为3000像素。我正在将它们调整为200像素长以显示它们


加快速度的最佳方法是什么?

我不久前也遇到了同样的问题(在为摄影师提供服务时,使用angular)

问题不在于RxJS或angular,而在于浏览器本身——它不适合以这种方式显示大量大图像

首先,如果需要显示大量图像(无论是本地文件还是远程文件):

  • 在显示之前调整大小(加载速度更快,无需调整大小,内存消耗更低)
  • 若您可以-只显示可见的图像(否则页面将非常慢,直到所有图像都将被加载)。检查此答案:最初
    trackVisibility
    用于仅在图像可见时显示图像

  • 关于显示本地文件中的图像,事情更加复杂:

    在您的例子中,您将文件作为数据URL加载,但存在一个问题:您提到的70个图像(每个3MB)将消耗至少2.1GB的RAM(实际上更多,而且会影响性能)

    第一个建议是——如果可以的话:不要使用数据URL,最好在不需要的时候使用

    第二:如果您只需要缩略图,请在显示图像之前在本地调整图像大小(使用画布)。如果反走样对您的情况很重要,那么可能会出现反走样问题—请查看此处描述的降压技术:如果您支持iOS,则画布大小限制可能会出现问题,因此您需要以某种方式进行检测。(这两个问题都在下面的例子中得到了解决)

    最后一点:如果您需要为大量图像创建缩略图,请不要立即创建缩略图,而是通过事件循环安排工作(否则,在调整图像大小时,浏览器将不响应)。为了获得更好的性能:按顺序执行(不是对所有图像并行执行),这听起来可能很奇怪,但速度会更快(因为内存消耗太低,同时磁盘读取更少)

    总之:

  • 使用上述
    trackVisibility
    指令仅显示可见图像
  • 不要使用数据URL,尤其是大图像
  • 在显示调整大小的缩略图之前创建它们
  • 您可能会发现库对于实现此功能非常有用:

    关于制作图像缩略图的粗略代码示例(大多数代码都是从工作项目中复制的-因此,它应该可以工作。
    canvasToJpegBlob
    makethumboil
    刚刚编写,没有经过测试,所以可能会有小错误):

    函数加载图像(imagePath){
    返回Rx.Observable.create(函数(观察者){
    var img=新图像();
    img.src=图像路径;
    image.onload=函数(){
    observer.onNext(图片);
    observer.onCompleted();
    }
    image.onError=函数(err){
    观察者:ONERR(err);
    }
    });
    }
    //画布边缘检测
    var maxDimm=32000;
    var ios5=假,ios3=假;
    (功能(){
    if(navigator.userAgent.indexOf('MSIE')!=-1 | | navigator.appVersion.indexOf('Trident/'))>0){
    maxDimm=8000;
    }否则{
    var canvas=document.createElement('canvas');
    canvas.width=1024*3;
    canvas.height=1025;
    if(canvas.toDataURL('image/jpeg')=='data:,'){
    ios3=真;
    }否则{
    canvas=document.createElement('canvas');
    画布宽度=1024*5;
    canvas.height=1025;
    if(canvas.toDataURL('image/jpeg')=='data:,'){
    ios5=真;
    }
    }
    }
    }());
    函数
    
            <div
                ng-repeat="photo in controller.pendingPhotos"
                class="mdl-card photo-frame mdl-card--border mdl-shadow--4dp">
                <div class="mdl-card__title">
                    {{photo.file.name}}
                </div>
    
                <div class="img-placeholder mdl-card__media">
                    <div
                        ng-if="!photo.content"
                        class="mdl-spinner mdl-js-spinner is-active"
                        mdl-upgrade
                        ></div>
    
                    <img class="img-preview" ng-if="photo.content" ng-src="{{photo.content}}"/>
                </div>
    
                <div class="mdl-card__supporting-text" ng-if="photo.response">
                    {{controller.formatResponse(photo.response)}}
                </div>
    
            </div>
    
     function loadImage(imagePath) {
       return Rx.Observable.create(function(observer) {
         var img = new Image();
         img.src = imagePath;
         image.onload = function() {
           observer.onNext(image);
           observer.onCompleted();
         }
         image.onError = function(err) {
           observer.onError(err);
         }
       });
     }
    
     // canvas edge cases detection
     var maxDimm = 32000;
     var ios5 = false, ios3 = false;
     (function() {
       if (navigator.userAgent.indexOf('MSIE') !== -1 || navigator.appVersion.indexOf('Trident/') > 0) {
         maxDimm = 8000;
       } else {
         var canvas = document.createElement('canvas');
         canvas.width = 1024 * 3;
         canvas.height = 1025;
         if (canvas.toDataURL('image/jpeg') === 'data:,') {
           ios3 = true;
         } else {
           canvas = document.createElement('canvas');
           canvas.width = 1024 * 5;
           canvas.height = 1025;
           if (canvas.toDataURL('image/jpeg') === 'data:,') {
             ios5 = true;
           }
         }
       }
     }());
    
     function stepDown(src, width, height) {
       var
         steps,
         resultCanvas = document.createElement('canvas'),
         srcWidth = src.width,
         srcHeight = src.height,
         context;
    
       resultCanvas.width = width;
       resultCanvas.height = height;
    
       if ((srcWidth / width) > (srcHeight / height)) {
         steps = Math.ceil(Math.log(srcWidth / width) / Math.log(2));
       } else {
         steps = Math.ceil(Math.log(srcHeight / height) / Math.log(2));
       }
    
       if (steps <= 1) {
         context = resultCanvas.getContext('2d');
         context.drawImage(src, 0, 0, width, height);
       } else {
         var tmpCanvas = document.createElement('canvas');
    
         var
           currentWidth = width * Math.pow(2, steps - 1),
           currentHeight = height * Math.pow(2, steps - 1),
           newWidth = currentWidth,
           newHeight = currentHeight;
    
         if (ios3 && currentWidth * currentHeight > 3 * 1024 * 1024) {
           newHeight = 1024 * Math.sqrt(3 * srcHeight / srcWidth);
           newWidth = newHeight * srcWidth / srcHeight;
         } else {
           if (ios5 && currentWidth * currentHeight > 5 * 1024 * 1024) {
             newHeight = 1024 * Math.sqrt(5 * srcHeight / srcWidth);
             newWidth = newHeight * srcWidth / srcHeight;
           } else {
             if (currentWidth > maxDimm || currentHeight > maxDimm) {
               if (currentHeight > currentWidth) {
                 newHeight = maxDimm;
                 newWidth = maxDimm * currentWidth / currentHeight;
               } else {
                 newWidth = maxDimm;
                 newHeight = maxDimm * currentWidth / currentHeight;
               }
             }
           }
         }
    
         currentWidth = newWidth;
         currentHeight = newHeight;
    
         if ((currentWidth / width) > (currentHeight / height)) {
           steps = Math.ceil(Math.log(currentWidth / width) / Math.log(2));
         } else {
           steps = Math.ceil(Math.log(currentHeight / height) / Math.log(2));
         }
    
    
         context = tmpCanvas.getContext('2d');
         tmpCanvas.width = Math.ceil(currentWidth);
         tmpCanvas.height = Math.ceil(currentHeight);
    
         context.drawImage(src, 0, 0, srcWidth, srcHeight, 0, 0, currentWidth, currentHeight);
    
         while (steps > 1) {
           newWidth = currentWidth * 0.5;
           newHeight = currentHeight * 0.5;
    
           context.drawImage(tmpCanvas, 0, 0, currentWidth, currentHeight, 0, 0, newWidth, newHeight);
           steps -= 1;
           currentWidth = newWidth;
           currentHeight = newHeight;
         }
    
         context = resultCanvas.getContext('2d');
         context.drawImage(tmpCanvas, 0, 0, currentWidth, currentHeight, 0, 0, width, height);
       }
       return resultCanvas;
     }
    
     function canvasToJpegBlob(canvas) {
       return Rx.Observable.create(function(observer) {
         try {
           canvas.toBlob(function(blob) {
             observer.onNext(blob);
             observer.onCompleted();
           }, 'image/jpeg');
         } catch (err) {
           observer.onError(err);
         }
       });
     }
    
     function makeThumbnail(file) {
       return Observable.defer(()=> {
         const fileUrl = URL.createObjectURL(file);
         return loadImage(fileUrl)
           .map(image => {
             const width = 200;
             const height = image.height * width / image.width;
             const thumbnailCanvas = stepDown(image, width, height);
             URL.revokeObjectURL(fileUrl);
             return thubnailCanvas;
           })
           .flatMap(canvasToJpegBlob)
           .map(canvasBlob=>URL.createObjectURL(canvasBlob))
           .map(thumbnailUrl => {
             return {
               file,
               thumbnailUrl
             }
           })
       });
      }