From bebdcec7f104a1ef953516d8f3b5d8878b4733be Mon Sep 17 00:00:00 2001 From: MinhHNguyen Date: Thu, 24 Aug 2017 16:30:29 -0700 Subject: [PATCH] Fix: IE11 image size default (#333) --- src/lib/viewers/image/ImageBaseViewer.js | 64 ++++++++++++------- src/lib/viewers/image/ImageViewer.js | 14 ++-- .../image/__tests__/ImageBaseViewer-test.js | 57 +++++++++++------ 3 files changed, 86 insertions(+), 49 deletions(-) diff --git a/src/lib/viewers/image/ImageBaseViewer.js b/src/lib/viewers/image/ImageBaseViewer.js index 95f9d96bc..77ae4e189 100644 --- a/src/lib/viewers/image/ImageBaseViewer.js +++ b/src/lib/viewers/image/ImageBaseViewer.js @@ -3,6 +3,7 @@ import Controls from '../../Controls'; import BaseViewer from '../BaseViewer'; import Browser from '../../Browser'; import { ICON_ZOOM_IN, ICON_ZOOM_OUT } from '../../icons/icons'; +import { get } from '../../util'; import { CLASS_INVISIBLE } from '../../constants'; @@ -43,13 +44,17 @@ class ImageBaseViewer extends BaseViewer { return; } - this.setOriginalImageSize(this.imageEl); - this.loadUI(); - this.zoom(); - - this.imageEl.classList.remove(CLASS_INVISIBLE); - this.loaded = true; - this.emit('load'); + const loadOriginalDimensions = this.setOriginalImageSize(this.imageEl); + loadOriginalDimensions + .then(() => { + this.loadUI(); + this.zoom(); + + this.imageEl.classList.remove(CLASS_INVISIBLE); + this.loaded = true; + this.emit('load'); + }) + .catch(this.errorHandler); } /** @@ -177,27 +182,38 @@ class ImageBaseViewer extends BaseViewer { * @return {Promise} A promise that is resolved if the original image dimensions were set. */ setOriginalImageSize(imageEl) { - const image = imageEl; const promise = new Promise((resolve, reject) => { // Do not bother loading a new image when the natural size attributes exist - if (imageEl.naturalWidth > 0 && imageEl.naturalHeight > 0) { - image.originalWidth = imageEl.naturalWidth; - image.originalHeight = imageEl.naturalHeight; + if (imageEl.naturalWidth && imageEl.naturalHeight) { + imageEl.setAttribute('originalWidth', imageEl.naturalWidth); + imageEl.setAttribute('originalHeight', imageEl.naturalHeight); resolve(); } else { - const originalImg = new Image(); - image.originalWidth = 1; - image.originalHeight = 1; - - originalImg.error = () => { - reject(); - }; - originalImg.onload = () => { - image.originalWidth = originalImg.width || 1; - image.originalHeight = originalImg.height || 1; - resolve(); - }; - originalImg.src = imageEl.src; + // Case when natural dimensions are not assigned + // By default, assigned width and height in Chrome/Safari/Firefox will be 300x150. + // IE11 workaround. Dimensions only displayed if the image is attached to the document. + get(imageEl.src, {}, 'text') + .then((imageAsText) => { + const parser = new DOMParser(); + const svgEl = parser.parseFromString(imageAsText, 'image/svg+xml'); + + try { + // Assume svgEl is an instanceof an SVG with a viewBox and preserveAspectRatio of meet + // where the height is the limiting axis + const viewBox = svgEl.documentElement.getAttribute('viewBox'); + const [, , w, h] = viewBox.split(' '); + const aspectRatio = h ? w / h : w; + imageEl.setAttribute('originalWidth', Math.round(aspectRatio * 150)); + imageEl.setAttribute('originalHeight', 150); + resolve(); + } catch (e) { + // Assume 300x150 that chrome does by default + imageEl.setAttribute('originalWidth', 300); + imageEl.setAttribute('originalHeight', 150); + resolve(e); + } + }) + .catch(reject); } }); diff --git a/src/lib/viewers/image/ImageViewer.js b/src/lib/viewers/image/ImageViewer.js index e4107e91d..490ba0f7f 100644 --- a/src/lib/viewers/image/ImageViewer.js +++ b/src/lib/viewers/image/ImageViewer.js @@ -183,10 +183,14 @@ class ImageViewer extends ImageBaseViewer { // If the image is smaller than the new viewport, zoom up to a // max of the original file size } else if (modifyWidthInsteadOfHeight) { - const originalWidth = this.isRotated() ? this.imageEl.originalHeight : this.imageEl.originalWidth; + const originalWidth = this.isRotated() + ? this.imageEl.getAttribute('originalHeight') + : this.imageEl.getAttribute('originalWidth'); newWidth = Math.min(viewport.width, originalWidth); } else { - const originalHeight = this.isRotated() ? this.imageEl.originalWidth : this.imageEl.originalHeight; + const originalHeight = this.isRotated() + ? this.imageEl.getAttribute('originalWidth') + : this.imageEl.getAttribute('originalHeight'); newHeight = Math.min(viewport.height, originalHeight); } } @@ -231,7 +235,9 @@ class ImageViewer extends ImageBaseViewer { * @return {void} */ setScale(width, height) { - this.scale = width ? width / this.imageEl.originalWidth : height / this.imageEl.originalHeight; + this.scale = width + ? width / this.imageEl.getAttribute('originalWidth') + : height / this.imageEl.getAttribute('originalHeight'); this.rotationAngle = this.currentRotationAngle % 3600 % 360; this.emit('scale', { scale: this.scale, @@ -390,7 +396,7 @@ class ImageViewer extends ImageBaseViewer { handleOrientationChange() { this.adjustImageZoomPadding(); - this.scale = this.imageEl.clientWidth / this.imageEl.originalWidth; + this.scale = this.imageEl.clientWidth / this.imageEl.getAttribute('originalWidth'); this.rotationAngle = this.currentRotationAngle % 3600 % 360; this.emit('scale', { scale: this.scale, diff --git a/src/lib/viewers/image/__tests__/ImageBaseViewer-test.js b/src/lib/viewers/image/__tests__/ImageBaseViewer-test.js index 98aa59848..50c8831f4 100644 --- a/src/lib/viewers/image/__tests__/ImageBaseViewer-test.js +++ b/src/lib/viewers/image/__tests__/ImageBaseViewer-test.js @@ -3,6 +3,7 @@ import ImageBaseViewer from '../ImageBaseViewer'; import BaseViewer from '../../BaseViewer'; import Browser from '../../../Browser'; import fullscreen from '../../../Fullscreen'; +import * as util from '../../../util'; import { ICON_ZOOM_IN, ICON_ZOOM_OUT } from '../../../icons/icons'; const CSS_CLASS_PANNING = 'panning'; @@ -11,8 +12,6 @@ const CSS_CLASS_PANNABLE = 'pannable'; let stubs = {}; -const imageUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAKQWlDQ1BJQ0MgUHJvZmlsZQAASA2dlndUU9kWh8+9N73QEiIgJfQaegkg0jtIFQRRiUmAUAKGhCZ2RAVGFBEpVmRUwAFHhyJjRRQLg4Ji1wnyEFDGwVFEReXdjGsJ7601896a/cdZ39nnt9fZZ+9917oAUPyCBMJ0WAGANKFYFO7rwVwSE8vE9wIYEAEOWAHA4WZmBEf4RALU/L09mZmoSMaz9u4ugGS72yy/UCZz1v9/kSI3QyQGAApF1TY8fiYX5QKUU7PFGTL/BMr0lSkyhjEyFqEJoqwi48SvbPan5iu7yZiXJuShGlnOGbw0noy7UN6aJeGjjAShXJgl4GejfAdlvVRJmgDl9yjT0/icTAAwFJlfzOcmoWyJMkUUGe6J8gIACJTEObxyDov5OWieAHimZ+SKBIlJYqYR15hp5ejIZvrxs1P5YjErlMNN4Yh4TM/0tAyOMBeAr2+WRQElWW2ZaJHtrRzt7VnW5mj5v9nfHn5T/T3IevtV8Sbsz55BjJ5Z32zsrC+9FgD2JFqbHbO+lVUAtG0GQOXhrE/vIADyBQC03pzzHoZsXpLE4gwnC4vs7GxzAZ9rLivoN/ufgm/Kv4Y595nL7vtWO6YXP4EjSRUzZUXlpqemS0TMzAwOl89k/fcQ/+PAOWnNycMsnJ/AF/GF6FVR6JQJhIlou4U8gViQLmQKhH/V4X8YNicHGX6daxRodV8AfYU5ULhJB8hvPQBDIwMkbj96An3rWxAxCsi+vGitka9zjzJ6/uf6Hwtcim7hTEEiU+b2DI9kciWiLBmj34RswQISkAd0oAo0gS4wAixgDRyAM3AD3iAAhIBIEAOWAy5IAmlABLJBPtgACkEx2AF2g2pwANSBetAEToI2cAZcBFfADXALDIBHQAqGwUswAd6BaQiC8BAVokGqkBakD5lC1hAbWgh5Q0FQOBQDxUOJkBCSQPnQJqgYKoOqoUNQPfQjdBq6CF2D+qAH0CA0Bv0BfYQRmALTYQ3YALaA2bA7HAhHwsvgRHgVnAcXwNvhSrgWPg63whfhG/AALIVfwpMIQMgIA9FGWAgb8URCkFgkAREha5EipAKpRZqQDqQbuY1IkXHkAwaHoWGYGBbGGeOHWYzhYlZh1mJKMNWYY5hWTBfmNmYQM4H5gqVi1bGmWCesP3YJNhGbjS3EVmCPYFuwl7ED2GHsOxwOx8AZ4hxwfrgYXDJuNa4Etw/XjLuA68MN4SbxeLwq3hTvgg/Bc/BifCG+Cn8cfx7fjx/GvyeQCVoEa4IPIZYgJGwkVBAaCOcI/YQRwjRRgahPdCKGEHnEXGIpsY7YQbxJHCZOkxRJhiQXUiQpmbSBVElqIl0mPSa9IZPJOmRHchhZQF5PriSfIF8lD5I/UJQoJhRPShxFQtlOOUq5QHlAeUOlUg2obtRYqpi6nVpPvUR9Sn0vR5Mzl/OX48mtk6uRa5Xrl3slT5TXl3eXXy6fJ18hf0r+pvy4AlHBQMFTgaOwVqFG4bTCPYVJRZqilWKIYppiiWKD4jXFUSW8koGStxJPqUDpsNIlpSEaQtOledK4tE20Otpl2jAdRzek+9OT6cX0H+i99AllJWVb5SjlHOUa5bPKUgbCMGD4M1IZpYyTjLuMj/M05rnP48/bNq9pXv+8KZX5Km4qfJUilWaVAZWPqkxVb9UU1Z2qbapP1DBqJmphatlq+9Uuq43Pp893ns+dXzT/5PyH6rC6iXq4+mr1w+o96pMamhq+GhkaVRqXNMY1GZpumsma5ZrnNMe0aFoLtQRa5VrntV4wlZnuzFRmJbOLOaGtru2nLdE+pN2rPa1jqLNYZ6NOs84TXZIuWzdBt1y3U3dCT0svWC9fr1HvoT5Rn62fpL9Hv1t/ysDQINpgi0GbwaihiqG/YZ5ho+FjI6qRq9Eqo1qjO8Y4Y7ZxivE+41smsImdSZJJjclNU9jU3lRgus+0zwxr5mgmNKs1u8eisNxZWaxG1qA5wzzIfKN5m/krCz2LWIudFt0WXyztLFMt6ywfWSlZBVhttOqw+sPaxJprXWN9x4Zq42Ozzqbd5rWtqS3fdr/tfTuaXbDdFrtOu8/2DvYi+yb7MQc9h3iHvQ732HR2KLuEfdUR6+jhuM7xjOMHJ3snsdNJp9+dWc4pzg3OowsMF/AX1C0YctFx4bgccpEuZC6MX3hwodRV25XjWuv6zE3Xjed2xG3E3dg92f24+ysPSw+RR4vHlKeT5xrPC16Il69XkVevt5L3Yu9q76c+Oj6JPo0+E752vqt9L/hh/QL9dvrd89fw5/rX+08EOASsCegKpARGBFYHPgsyCRIFdQTDwQHBu4IfL9JfJFzUFgJC/EN2hTwJNQxdFfpzGC4sNKwm7Hm4VXh+eHcELWJFREPEu0iPyNLIR4uNFksWd0bJR8VF1UdNRXtFl0VLl1gsWbPkRoxajCCmPRYfGxV7JHZyqffS3UuH4+ziCuPuLjNclrPs2nK15anLz66QX8FZcSoeGx8d3xD/iRPCqeVMrvRfuXflBNeTu4f7kufGK+eN8V34ZfyRBJeEsoTRRJfEXYljSa5JFUnjAk9BteB1sl/ygeSplJCUoykzqdGpzWmEtPi000IlYYqwK10zPSe9L8M0ozBDuspp1e5VE6JA0ZFMKHNZZruYjv5M9UiMJJslg1kLs2qy3mdHZZ/KUcwR5vTkmuRuyx3J88n7fjVmNXd1Z752/ob8wTXuaw6thdauXNu5Tnddwbrh9b7rj20gbUjZ8MtGy41lG99uit7UUaBRsL5gaLPv5sZCuUJR4b0tzlsObMVsFWzt3WazrWrblyJe0fViy+KK4k8l3JLr31l9V/ndzPaE7b2l9qX7d+B2CHfc3em681iZYlle2dCu4F2t5czyovK3u1fsvlZhW3FgD2mPZI+0MqiyvUqvakfVp+qk6oEaj5rmvep7t+2d2sfb17/fbX/TAY0DxQc+HhQcvH/I91BrrUFtxWHc4azDz+ui6rq/Z39ff0TtSPGRz0eFR6XHwo911TvU1zeoN5Q2wo2SxrHjccdv/eD1Q3sTq+lQM6O5+AQ4ITnx4sf4H++eDDzZeYp9qukn/Z/2ttBailqh1tzWibakNml7THvf6YDTnR3OHS0/m/989Iz2mZqzymdLz5HOFZybOZ93fvJCxoXxi4kXhzpXdD66tOTSna6wrt7LgZevXvG5cqnbvfv8VZerZ645XTt9nX297Yb9jdYeu56WX+x+aem172296XCz/ZbjrY6+BX3n+l37L972un3ljv+dGwOLBvruLr57/17cPel93v3RB6kPXj/Mejj9aP1j7OOiJwpPKp6qP6391fjXZqm99Oyg12DPs4hnj4a4Qy//lfmvT8MFz6nPK0a0RupHrUfPjPmM3Xqx9MXwy4yX0+OFvyn+tveV0auffnf7vWdiycTwa9HrmT9K3qi+OfrW9m3nZOjk03dp76anit6rvj/2gf2h+2P0x5Hp7E/4T5WfjT93fAn88ngmbWbm3/eE8/syOll+AAAA4klEQVQoFWNkQABG7QlvU/7/Z0xmYGTQBgv/Z7jKyPh/7tUC4TlA/n+QGCOI0Ox/LcnIwLKEkZHBCcRHB///M+z7z/An5nqh6HMmoCRQHapiN1VWhlZXLrg+kEEgNSC1LCBnABkoJvOyMTKIcoMtR9EEUsuo1f/uBCMjozlcBg/j////J5ngHkRS6KrCylDvxIkkAmUCAwPkBwygIszE4KDEiiEOEmACBtZVrDLYBIFqmUDhjE0OmxhILSgogB5/vwdXHMA0guLiWqGgC8gPQPafGJAATBKdhkUcSC1yYBOVNAAVx0qxuz8xqgAAAABJRU5ErkJggg=='; - const sandbox = sinon.sandbox.create(); let imageBase; let containerEl; @@ -224,31 +223,41 @@ describe('lib/viewers/image/ImageBaseViewer', () => { it('should use the naturalHeight and naturalWidth when available', (done) => { const imageEl = { naturalWidth: 100, - naturalHeight: 100 + naturalHeight: 100, + setAttribute: (name, value) => { + imageEl[name] = value; + }, + getAttribute: (name) => imageEl[name] }; const promise = imageBase.setOriginalImageSize(imageEl); promise.should.be.fulfilled.then(() => { - expect(imageEl.originalWidth).to.equal(imageEl.naturalWidth); - expect(imageEl.originalHeight).to.equal(imageEl.naturalHeight); + expect(imageEl.getAttribute('originalWidth')).to.equal(imageEl.naturalWidth); + expect(imageEl.getAttribute('originalHeight')).to.equal(imageEl.naturalHeight); done(); + }).catch(() => { + Assert.fail(); }); }); - it('should work when naturalHeight and naturalWidth are undefined', (done) => { + it('should default to 300x150 when naturalHeight and naturalWidth are 0x0', (done) => { const imageEl = { - naturalWidth: undefined, - naturalHeight: undefined, - src: imageUrl + naturalWidth: 0, + naturalHeight: 0, + setAttribute: (name, value) => { + imageEl[name] = value; + }, + getAttribute: (name) => imageEl[name] }; - const imageUrlWidth = 12; - const imageUrlHeight = 12; + const getStub = sandbox.stub(util, 'get').returns(Promise.resolve('not real a image')); const promise = imageBase.setOriginalImageSize(imageEl); promise.should.be.fulfilled.then(() => { - expect(imageEl.originalWidth).to.equal(imageUrlWidth); - expect(imageEl.originalHeight).to.equal(imageUrlHeight); + expect(imageEl.getAttribute('originalWidth')).to.equal(300); + expect(imageEl.getAttribute('originalHeight')).to.equal(150); done(); + }).catch(() => { + Assert.fail(); }); }); }); @@ -532,14 +541,15 @@ describe('lib/viewers/image/ImageBaseViewer', () => { describe('finishLoading()', () => { beforeEach(() => { imageBase.loaded = false; - stubs.emit = sandbox.stub(imageBase, 'emit'); stubs.zoom = sandbox.stub(imageBase, 'zoom'); stubs.loadUI = sandbox.stub(imageBase, 'loadUI'); stubs.setOriginalImageSize = sandbox.stub(imageBase, 'setOriginalImageSize'); + stubs.errorHandler = sandbox.stub(imageBase, 'errorHandler'); }); it('should do nothing if already destroyed', () => { imageBase.destroyed = true; + stubs.emit = sandbox.stub(imageBase, 'emit'); imageBase.finishLoading(); expect(imageBase.loaded).to.be.false; @@ -547,17 +557,22 @@ describe('lib/viewers/image/ImageBaseViewer', () => { expect(stubs.zoom).to.not.have.been.called; expect(stubs.setOriginalImageSize).to.not.have.been.called; expect(stubs.loadUI).to.not.have.been.called; + expect(stubs.errorHandler).to.not.have.been.called; }); - it('should load UI if not destroyed', () => { - imageBase.finishLoading(); + it('should load UI if not destroyed', (done) => { + imageBase.on('load', () => { + expect(stubs.errorHandler).to.not.have.been.called; + expect(imageBase.loaded).to.be.true; + expect(stubs.zoom).to.have.been.called; + expect(stubs.loadUI).to.have.been.called; + done(); + }); + stubs.setOriginalImageSize.returns(Promise.resolve()); + imageBase.destroyed = false; - expect(imageBase.loaded).to.be.true; - expect(stubs.emit).to.have.been.calledWith('load'); - expect(stubs.emit).to.have.been.calledWith('load'); + imageBase.finishLoading(); expect(stubs.setOriginalImageSize).to.have.been.called; - expect(stubs.zoom).to.have.been.called; - expect(stubs.loadUI).to.have.been.called; }); });