diff --git a/extensions/amp-image-lightbox/0.1/amp-image-lightbox.js b/extensions/amp-image-lightbox/0.1/amp-image-lightbox.js index 015c76bc3979..55003200126a 100644 --- a/extensions/amp-image-lightbox/0.1/amp-image-lightbox.js +++ b/extensions/amp-image-lightbox/0.1/amp-image-lightbox.js @@ -48,7 +48,7 @@ const SUPPORTED_ELEMENTS_ = { }; /** @private @const {!../../../src/curve.CurveDef} */ -const ENTER_CURVE_ = bezierCurve(0.4, -0.3, 0.2, 1); +const ENTER_CURVE_ = bezierCurve(0.4, 0, 0.2, 1); /** @private @const {!../../../src/curve.CurveDef} */ const EXIT_CURVE_ = bezierCurve(0.4, 0, 0.2, 1); @@ -913,27 +913,34 @@ class AmpImageLightbox extends AMP.BaseElement { const rect = layoutRectFromDomRect(this.sourceImage_ ./*OK*/getBoundingClientRect()); + const imageBox = this.imageViewer_.getImageBox(); const clone = this.sourceImage_.cloneNode(true); + clone.className = ''; st.setStyles(clone, { position: 'absolute', top: st.px(rect.top), left: st.px(rect.left), width: st.px(rect.width), height: st.px(rect.height), + transformOrigin: 'top left', + willChange: 'transform', }); transLayer.appendChild(clone); this.sourceImage_.classList.add('-amp-ghost'); - // Move the image to the location given by the lightbox. - const imageBox = this.imageViewer_.getImageBox(); + // Move and resize the image to the location given by the lightbox. const dx = imageBox.left - rect.left; const dy = imageBox.top - rect.top; + const scaleX = rect.width != 0 ? imageBox.width / rect.width : 1; // Duration will be somewhere between 0.2 and 0.8 depending on how far // the image needs to move. const motionTime = Math.max(0.2, Math.min(0.8, Math.abs(dy) / 250 * 0.8)); anim.add(0, tr.setStyles(clone, { - transform: tr.translate(tr.numeric(0, dx), tr.numeric(0, dy)), + transform: tr.concat([ + tr.translate(tr.numeric(0, dx), tr.numeric(0, dy)), + tr.scale(tr.numeric(1, scaleX)), + ]), }), motionTime, ENTER_CURVE_); // Fade in the container. This will mostly affect the caption. @@ -983,16 +990,16 @@ class AmpImageLightbox extends AMP.BaseElement { const rect = layoutRectFromDomRect(this.sourceImage_ ./*OK*/getBoundingClientRect()); - const newLeft = imageBox.left + (imageBox.width - rect.width) / 2; - const newTop = imageBox.top + (imageBox.height - rect.height) / 2; const clone = image.cloneNode(true); st.setStyles(clone, { position: 'absolute', - top: st.px(newTop), - left: st.px(newLeft), - width: st.px(rect.width), - height: st.px(rect.height), + top: st.px(imageBox.top), + left: st.px(imageBox.left), + width: st.px(imageBox.width), + height: st.px(imageBox.height), transform: '', + transformOrigin: 'top left', + willChange: 'transform', }); transLayer.appendChild(clone); @@ -1001,19 +1008,24 @@ class AmpImageLightbox extends AMP.BaseElement { opacity: tr.numeric(1, 0), }), 0.1, EXIT_CURVE_); - // Move the image back to where it is in the article. - const dx = rect.left - newLeft; - const dy = rect.top - newTop; + // Move and resize the image back to where it is in the article. + const dx = rect.left - imageBox.left; + const dy = rect.top - imageBox.top; + const scaleX = imageBox.width != 0 ? rect.width / imageBox.width : 1; /** @const {!TransitionDef} */ - const move = tr.setStyles(clone, { - transform: tr.translate(tr.numeric(0, dx), tr.numeric(0, dy)), + const moveAndScale = tr.setStyles(clone, { + transform: tr.concat([ + tr.translate(tr.numeric(0, dx), tr.numeric(0, dy)), + tr.scale(tr.numeric(1, scaleX)), + ]), }); + // Duration will be somewhere between 0.2 and 0.8 depending on how far // the image needs to move. Start the motion later too, but no later // than 0.2. const motionTime = Math.max(0.2, Math.min(0.8, Math.abs(dy) / 250 * 0.8)); anim.add(Math.min(0.8 - motionTime, 0.2), (time, complete) => { - move(time); + moveAndScale(time); if (complete) { this.sourceImage_.classList.remove('-amp-ghost'); } diff --git a/src/transition.js b/src/transition.js index c5684f87bb65..54a8ab23cea7 100644 --- a/src/transition.js +++ b/src/transition.js @@ -38,6 +38,28 @@ export function all(transitions) { } +/** + * Returns a transition that combines the string result of other string-based + * transitions such as transform and scale using the given opt_delimiter. + * @param {!Array>} transitions + * @param {string=} opt_delimiter Defaults to a single whitespace. + * @return {!TransitionDef} + */ +export function concat(transitions, opt_delimiter = ' ') { + return (time, complete) => { + const results = []; + for (let i = 0; i < transitions.length; i++) { + const tr = transitions[i]; + const result = tr(time, complete); + if (typeof result == 'string') { + results.push(result); + } + } + return results.join(opt_delimiter); + }; +} + + /** * Returns the specified transition with the time curved via specified curve * function. diff --git a/test/functional/test-transition.js b/test/functional/test-transition.js index 24b4b6fc24ea..24e19bfaccc5 100644 --- a/test/functional/test-transition.js +++ b/test/functional/test-transition.js @@ -56,6 +56,55 @@ describe('Transition', () => { expect(func2.calledWithExactly(1, true)).to.equal(true); }); + describe('concat', () => { + it('should concat two string transitions', () => { + const t1 = tr.translateX(tr.numeric(0, 10)); + const t2 = tr.scale(tr.numeric(0, 10)); + const concat = tr.concat([t1, t2]); + + expect(concat(0, false)).to.equal('translateX(0px) scale(0)'); + expect(concat(0.5, false)).to.equal('translateX(5px) scale(5)'); + expect(concat(1, true)).to.equal('translateX(10px) scale(10)'); + }); + + it('should handle single transitions', () => { + const t1 = tr.translateX(tr.numeric(0, 10)); + const concat = tr.concat([t1]); + + expect(concat(0, false)).to.equal('translateX(0px)'); + expect(concat(0.5, false)).to.equal('translateX(5px)'); + expect(concat(1, true)).to.equal('translateX(10px)'); + }); + + it('should handle empty input', () => { + const concat = tr.concat([]); + + expect(concat(0, false)).to.equal(''); + expect(concat(0.5, false)).to.equal(''); + expect(concat(1, true)).to.equal(''); + }); + + it('should ignore non-string transitions', () => { + const t1 = tr.translateX(tr.numeric(0, 10)); + const t2 = tr.spring(2, 10, 12, 0.8); + const concat = tr.concat([t1, t2]); + + expect(concat(0, false)).to.equal('translateX(0px)'); + expect(concat(0.5, false)).to.equal('translateX(5px)'); + expect(concat(1, true)).to.equal('translateX(10px)'); + }); + + it('should support other delimeters', () => { + const t1 = tr.px(tr.numeric(0, 10)); + const t2 = tr.px(tr.numeric(0, 20)); + const concat = tr.concat([t1, t2], ', '); + + expect(concat(0, false)).to.equal('0px, 0px'); + expect(concat(0.5, false)).to.equal('5px, 10px'); + expect(concat(1, true)).to.equal('10px, 20px'); + }); + }); + it('withCurve', () => { const func1 = (time, complete) => `${time * 2};${complete}`; const curve = unusedTime => 0.2; diff --git a/test/manual/amp-image-lightbox.amp.html b/test/manual/amp-image-lightbox.amp.html index 4523f553736d..1aaecca1daa0 100644 --- a/test/manual/amp-image-lightbox.amp.html +++ b/test/manual/amp-image-lightbox.amp.html @@ -48,6 +48,15 @@ color: #aaa; height: 60px; } + + .row { + display: flex; + flex-direction: row; + } + + .row > div { + flex: 1; + } @@ -87,12 +96,25 @@

Image Lightbox

Lorem ipsum dolor sit amet, has nisl nihil convenire et, vim at aeque inermis reprehendunt. Propriae tincidunt id nec, elit nusquam te mea, ius noster platonem in. Mea an idque minim, sit sale deleniti apeirian et. Omnium legendos tractatos cu mea. Vix in stet dolorem accusamus. Iisque rationibus consetetur in cum, quo unum nulla legere ut. Simul numquam saperet no sit.

- +
+ +
+
+ +
+
Iisque rationibus consetetur in cum, quo unum nulla legere ut. Simul numquam saperet no sit.