如何在html/javascript中加速显示本地文件系统中的大型图像
我有一个html应用程序,我正在处理大量的大图像。我们谈论的可能是5000张照片,每张大约3-5MB 到目前为止,我正在测试大约1000张图片,而且速度已经相当慢了 我使用拖放和文件读取器加载图像,然后将文件读取器结果设置为图像源:如何在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( () => {
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
指令仅显示可见图像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
}
})
});
}