如何在HTML / JavaScript中加快本地文件系统中大图像的显示速度
how to speed up display of large images from local file system in html / javascript
我有一个html应用程序,我正在处理大量的大图像。我们谈论的可能是 5,000 张照片,每张照片大约 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 ) );
} );
}
.html:
<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>
我知道ng-repeat可能是一个性能问题,我会对此进行排序,但目前即使显示一张图像也可能需要几秒钟。如果我从光盘加载图像但实际上没有显示它,则从光盘加载每个图像只需要大约 50-100 毫秒。如果我显示它,事情会变慢得多。
我怀疑速度变慢是浏览器(铬)必须调整图像大小。
在我对 70 张图像进行的测试中,我将它们全部加载到浏览器中,在加载所有内容并渲染后,前几次我上下滚动页面时滚动性能很慢,之后它很流畅。
这些图像约为 3,000 x 2,000 像素。我正在将它们的大小调整为 200 像素长以显示它们。
加快速度的最佳方法是什么?
前段时间我遇到了同样的问题(在为摄影师提供服务时,使用角度)。
问题不在于 RxJS 或角度,而在于浏览器本身 - 它没有针对以这种方式显示大量大图像进行优化。
首先,如果您需要显示大量图像(无论是本地文件还是远程文件):
- 在显示之前调整它们的大小(加载速度更快,无需调整大小,内存消耗更低)。
- 如果可以 - 仅显示可见图像(否则页面将非常慢,直到加载所有图像)。检查这个答案:如何在 AngularJS 指令中获取元素的 x 和 y 位置
trackVisibility
该指令最初编写为仅在图像可见时才显示图像。
关于显示本地文件中的图像,事情更加复杂:
在您的情况下,您将文件加载为数据 url,并且存在一个问题:您提到的 70 张图像,每张 3 mb 将消耗至少 2.1 Gb 的 RAM(实际上更多,并且会不知不觉地影响性能)
第一个建议是 - 如果可以的话:不要使用数据URL,最好使用URL.createObjectURL,并在不再需要时使用URL.revokeObjectURL。
第二:如果您只需要缩略图 - 在显示图像之前在本地(使用画布)调整图像大小。抗锯齿会有一个问题,如果它对你很重要 - 看看这里描述的降压技术:Html5 canvas drawImage:如何应用抗锯齿 如果你支持 iOS - 画布大小限制可能存在问题,所以你需要以某种方式检测它。(这两个问题都在下面的示例中得到解决)
最后一个:如果您需要为大量图像创建缩略图 - 不要一次执行此操作,而是通过事件循环安排工作(否则浏览器在调整图像大小时不会响应)。为了更好的性能:按顺序执行此操作(不是对所有图像并行),这听起来可能很奇怪 - 但是,它会更快(由于内存消耗太低,同时磁盘读取更少)。
总结:
- 使用上面提到的
trackVisibility
指令仅显示可见图像 - 不要使用,数据网址,特别是对于大图像。
- 在显示缩略图之前创建调整大小的缩略图
您可能会发现库对实现这一点很有用:
- https://github.com/blueimp/JavaScript-Load-Image
- https://github.com/blueimp/JavaScript-Canvas-to-Blob
关于做图像缩略图的粗略代码示例(大部分代码是从工作项目中复制的 - 所以,它应该工作。 canvasToJpegBlob
和makeThumbnail
是刚才写的,没有经过测试,所以可能会有小错误):
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
}
})
});
}
- qoxdoo中的离线存储是否与所有浏览器和本地文件系统兼容
- 内容安全策略指令:;脚本src'self'blob:文件系统:chrome扩展资源:“;获取是否时
- 是否有WebKit的实现可以方便地访问本地文件系统
- 使用 Javascript 在文件系统中打开 PDF
- Chrome/Tampermonkey 用户脚本存储在文件系统上的什么位置
- 在 Lua/Luci 服务器上使用 HTML/JavaScript 下载服务器根文件系统中的现有文件
- Html 5文件系统API,我得到一个DOMError“;NotSupporteError”;
- windows文件系统中的nodejs路径错误4058 ENOENT
- 在WinJS中将文件下载到文件系统
- NodeJ将文件从请求流式传输到文件系统,而不是内存
- 从firefox插件访问文件系统/目录路径
- 跨浏览器JS文件系统API
- 节点webkit windows文件系统分离器
- 如何使用chrome文件系统api从chrome应用程序中的url下载图像
- 如何在HTML / JavaScript中加快本地文件系统中大图像的显示速度
- 在 AngularJs 中将图像从文件系统发送到服务器
- 使用chrome文件系统toURL未显示图像
- 使用数据库与文件系统进行图像排序
- 递归延迟文件系统搜索图像
- 上传前查看从客户端文件系统中选择的图像