New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allows amp-img.js to check if it has a blurred placeholder child. #17132
Conversation
addieachan
commented
Jul 26, 2018
- Addresses issue Improve blurred background performance by using CSS painters #15146
- Uses an experimental flag
I know there are some lint errors that I will try and ask @rsimha about. Just wanted to get out the basic implementation |
builtins/amp-img.js
Outdated
@@ -47,6 +48,12 @@ export class AmpImg extends BaseElement { | |||
|
|||
/** @private {?UnlistenDef} */ | |||
this.unlistenError_ = null; | |||
|
|||
/** @private @const {boolean} */ | |||
this.useBlurryPlaceholder_ = isExperimentOn(this.win, 'blurry-placeholder'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ivar not needed, just inline this call below.
builtins/amp-img.js
Outdated
const placeholder = this.getPlaceholder(); | ||
if (placeholder) { | ||
const classes = placeholder.getAttribute('class').split(' '); | ||
if (classes.find(function(el) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use classList.contains()
instead.
builtins/amp-img.js
Outdated
if (classes.find(function(el) { | ||
return el == 'i-amphtml-blur'; | ||
})) { | ||
this.hasBlurredPlaceHolder_ = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's add the code that actually uses this boolean. This PR is still quite small.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So just to clarify, that would mean add the code to trigger the loading fade-in animation when the image is loaded, right?Or is it something else?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes.
builtins/amp-img.js
Outdated
@@ -16,6 +16,8 @@ | |||
|
|||
import {BaseElement} from '../src/base-element'; | |||
import {dev} from '../src/log'; | |||
import {isExperimentOn} from '../src/experiments'; | |||
import {guaranteeSrcForSrcsetUnsupportedBrowsers} from '../src/utils/img'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This and other calls to guaranteeSrcForSrcsetUnsupportedBrowsers were not implemented by me, but are in the current version of amp-img.js. It's just a two lines, but idk how important it is to ensure the changes that show up are only yours, so just wanted to point it out if you had advice on fixing it. Otherwise, I will try and solution as well!
Edit: I think it was resolved thanks to @choumx 's help!
test/functional/test-amp-img.js
Outdated
impl.buildCallback(); | ||
impl.layoutCallback(); | ||
expect(impl.element.classList.contains( | ||
'i-amphtml-blur-loading')).to.be.true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry if these expect statements are hard to read -I had to break them up to satisfy the lint rule of being <80 chars on a line. If there is a better place for me to break it/an alternate solution, then please let me know!
@choumx, @gmajoulet , @alanorozco I have the amp-img side of things functional for the image blur, and I feel like this PR is at a stage for a preliminary look so PTAL! |
builtins/amp-img.js
Outdated
@@ -26,7 +27,7 @@ import {registerElement} from '../src/service/custom-element-registry'; | |||
* @type {!Array<string>} | |||
*/ | |||
const ATTRIBUTES_TO_PROPAGATE = ['alt', 'title', 'referrerpolicy', 'aria-label', | |||
'aria-describedby', 'aria-labelledby','srcset', 'src', 'sizes']; | |||
'aria-describedby', 'aria-labelledby','srcset', 'src', 'sizes', 'blur']; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Revert this change.
builtins/amp-img.js
Outdated
@@ -146,7 +156,7 @@ export class AmpImg extends BaseElement { | |||
layoutCallback() { | |||
this.initialize_(); | |||
const img = dev().assertElement(this.img_); | |||
this.unlistenLoad_ = listen(img, 'load', () => this.hideFallbackImg_()); | |||
this.unlistenLoad_ = listen(img, 'load', () => this.renderImg_()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: onImgLoad_
builtins/amp-img.js
Outdated
@@ -167,6 +177,31 @@ export class AmpImg extends BaseElement { | |||
return true; | |||
} | |||
|
|||
/** | |||
* Checks to see if there is a placeholder that is blurred |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
builtins/amp-img.js
Outdated
} | ||
|
||
/** | ||
* @private |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Move annotations like @private
below the comment.
css/amp.css
Outdated
@@ -392,7 +410,7 @@ i-amphtml-sizer { | |||
display: block; | |||
} | |||
|
|||
.i-amphtml-element > [placeholder].hidden, | |||
.i-amphtml-element:not(.i-amphtml-blur-loaded, .i-amphtml-blur-loading) > [placeholder].hidden, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a comment here explaining this.
test/functional/test-amp-img.js
Outdated
impl.unlayoutCallback(); | ||
}); | ||
|
||
function getImgWithBlur(addPlacholder, addBlurClass) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo: addPlaceholder
builtins/amp-img.js
Outdated
*/ | ||
renderImg_() { | ||
if (this.hasBlurredPlaceHolder_) { | ||
this.element.classList.add('i-amphtml-blur-loaded'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should remove i-amphtml-blur-loading
right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh yeah! I think I thought that the blur would disappear before the fade in would complete, but I realize that wasn't the case. Thanks!
builtins/amp-img.js
Outdated
* placeholder if one exists, or hiding the fallback if not | ||
*/ | ||
renderImg_() { | ||
if (this.hasBlurredPlaceHolder_) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You won't need this ivar if you just check this.classList.contains('i-amphtml-blur-loading')
.
test/functional/test-amp-img.js
Outdated
const loadEvent = createCustomEvent(iframe.win, 'load'); | ||
impl.img_.dispatchEvent(loadEvent); | ||
expect(impl.element.classList.contains( | ||
'i-amphtml-blur-loaded')).to.be.true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Try using expect(el.classList).includes('i-amphtml-blur-loaded')
. This will yield a better error message on failure -- currently it'll look like "expected false to be true".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense! As a tangentially related question, is there a stylistic preference between using 'el.classList' vs 'impl.element.classList'?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We already have a reference to the element so just use it. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok cool!
Also btw, when I tried the 'expect(el.classList).includes('i-amphtml-blur-loaded')', the test failed because it said the object being tested was a domtokenlist which wasn't supported by the "includes()" call. Instead, I just did 'expect(el).to.have.class('i-amphtml-blur-loaded')' which seems to give the right error. Please let me know if this is a problem!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even better!
test/functional/test-amp-img.js
Outdated
impl = new AmpImg(el); | ||
impl.buildCallback(); | ||
impl.layoutCallback(); | ||
expect(impl.hasBlurredPlaceHolder_).to.be.false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Check CSS classes instead of ivars.
@choumx, @alanorozco, and @gmajoulet PTAL as I talked to Will on Friday, and over the weekend, I consolidated the blur implementation since we noticed that it was actually being rendered on top of the image so it had to be faded out. I also had to remove one test (that was testing the different classes given to the amp-img regarding hiding/unhiding of the image as it loads since it is now unnecessary to hide it). If there are any new tests that need to be added, then please let me know :) |
css/amp.css
Outdated
@@ -780,4 +791,4 @@ amp-story .i-amphtml-loader { | |||
*/ | |||
[amp-fx^="fly-in"] { | |||
visibility: hidden; | |||
} | |||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Restore trailing newline.
css/amp.css
Outdated
transition: opacity 1s 1s !important; | ||
} | ||
|
||
.i-amphtml-blur{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Space before {
.
css/amp.css
Outdated
@@ -23,7 +23,7 @@ | |||
* to be computed to `auto` on both the `body` and `html` elements so they both | |||
* potentially get a scrolling box. See #3108 for more details. | |||
*/ | |||
html { | |||
html { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Revert.
css/amp.css
Outdated
@@ -167,6 +167,16 @@ html.i-amphtml-singledoc.i-amphtml-ios-embed-sd > body { | |||
display: inline-block; | |||
} | |||
|
|||
.i-amphtml-fade-out .i-amphtml-blur { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i-amphtml-layout
class is added after layoutCallback()
, so can we avoid a custom CSS class altogether and do instead:
.i-amphtml-layout > .i-amphtml-blur {
opacity: 0 !important;
}
.i-amphtml-blur {
transition: ...;
filter: ...;
transform: ...;
}
css/amp.css
Outdated
@@ -167,6 +167,16 @@ html.i-amphtml-singledoc.i-amphtml-ios-embed-sd > body { | |||
display: inline-block; | |||
} | |||
|
|||
.i-amphtml-fade-out .i-amphtml-blur { | |||
opacity: 0 !important; | |||
transition: opacity 1s 1s !important; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why have a transition-delay of 1s? Also, let's use ease
timing function.
transition: opacity 1s ease !important;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I did it because I thought it looked better on fast connections where the image looked like it flashed since the placeholder wasn't seen. But the 'ease' function solves that problem so thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not aware of other AMP animation curves, but the material guidelines would recommend a "decelerating curve" here, either ease-out
or the actual material easing: cubic-bezier(0.0, 0.0, 0.2, 1)
.
builtins/amp-img.js
Outdated
const placeholder = this.getPlaceholder(); | ||
if (isExperimentOn(this.win, 'blurry-placeholder') && placeholder && | ||
placeholder.classList.contains('i-amphtml-blur')) { | ||
this.element.classList.add('i-amphtml-fade-out'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With my comment below, we actually might not need any changes here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So when I take out the overriding of fistLayoutCompleted(), the blur doesn't show up since the togglePlaceholder(false) is hiding it quickly not allowing for the fade out. Is it ok to keep the overriding just to make sure images with blurred placeholders don't call togglePlaceholder(false)?
builtins/amp-img.js
Outdated
firstLayoutCompleted() { | ||
const placeholder = this.getPlaceholder(); | ||
if (isExperimentOn(this.win, 'blurry-placeholder') && placeholder && | ||
placeholder.classList.contains('i-amphtml-blur')) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a little confused, how is i-amphtml-blur
set?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The devtool/cache generates it.
builtins/amp-img.js
Outdated
/** @override */ | ||
firstLayoutCompleted() { | ||
const placeholder = this.getPlaceholder(); | ||
if (isExperimentOn(this.win, 'blurry-placeholder') && placeholder && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tiny improvement: isExperimentOn
could read the cookies, which is an operation that's a bit more expensive than checking a class. Maybe it'd be worth moving the isExperimentOn()
check last?
css/amp.css
Outdated
@@ -167,6 +167,16 @@ html.i-amphtml-singledoc.i-amphtml-ios-embed-sd > body { | |||
display: inline-block; | |||
} | |||
|
|||
.i-amphtml-fade-out .i-amphtml-blur { | |||
opacity: 0 !important; | |||
transition: opacity 1s 1s !important; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not aware of other AMP animation curves, but the material guidelines would recommend a "decelerating curve" here, either ease-out
or the actual material easing: cubic-bezier(0.0, 0.0, 0.2, 1)
.
builtins/amp-img.js
Outdated
firstLayoutCompleted() { | ||
const placeholder = this.getPlaceholder(); | ||
if (!(placeholder && placeholder.classList.contains('i-amphtml-blur') && | ||
isExperimentOn(this.win, 'blurry-placeholder'))) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Flip the conditional to avoid the need for !
.
css/amp.css
Outdated
@@ -167,6 +167,13 @@ html.i-amphtml-singledoc.i-amphtml-ios-embed-sd > body { | |||
display: inline-block; | |||
} | |||
|
|||
|
|||
.i-amphtml-blur{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Space before {
.
test/functional/test-amp-img.js
Outdated
* @param {boolean} addBlurClass Whether the child should have the | ||
* class that allows it to be a blurred placeholder. | ||
*/ | ||
function getImgWithBlur(addPlaceholder, addBlurClass) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test code would be easier to follow if this helper was idempotent. I.e. change it to return the AmpImg
instead of modifying existing variables.
it('my test', () => {
const impl = getImgWithBlur(...);
const el = impl.element;
});
test/functional/test-amp-img.js
Outdated
} | ||
el.appendChild(img); | ||
el.getResources = () => Services.resourcesForDoc(document); | ||
el.getPlaceholder = sandbox.stub(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this return img
if addPlaceholder
is true? I.e. () => img
.
test/functional/test-amp-img.js
Outdated
impl.layoutWidth_ = 200; | ||
} | ||
|
||
it('should correctly detect if a blurry image child exists', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test name is out of date. How about "should set blurry placeholder opacity to 0 on image load"?
test/functional/test-amp-img.js
Outdated
impl.buildCallback(); | ||
impl.layoutCallback(); | ||
impl.firstLayoutCompleted(); | ||
expect(img.style.opacity).to.be.equal(''); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's also verify that togglePlaceholder(false)
continues to be called in the default case, here and below.
test/functional/test-amp-img.js
Outdated
expect(img.style.opacity).to.be.equal(''); | ||
const loadEvent = createCustomEvent(iframe.win, 'load'); | ||
impl.img_.dispatchEvent(loadEvent); | ||
impl.firstLayoutCompleted(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dispatching the event is no longer necessary. You can probably just remove this test.
PTAL whenever you guys are free! |
css/amp.css
Outdated
filter: blur(20px) !important; | ||
transform: scale(1.1) !important; | ||
transition: opacity 1s ease !important; | ||
transition: opacity 1s cubic-bezier(0.0, 0.0, 0.2, 1) !important; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why cubic-bezier
and not ease
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I actually asked for it in the previous review, that's the material deceleration curve.
However, I believe the animation should not last more than 300ms
test/functional/test-amp-img.js
Outdated
if (addPlaceholder) { | ||
img.setAttribute('placeholder', ''); | ||
el.getPlaceholder = () => {return img;}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor: You can use closure shorthand here. e.getPlaceholder = () => img;
test/functional/test-amp-img.js
Outdated
impl.layoutWidth_ = 200; | ||
|
||
impl.togglePlaceholder = sandbox.stub(); | ||
toggleExperiment(impl.win, 'blurry-placeholder', true, true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You only need to call this once in beforeEach
. impl.win == window
, so just do:
toggleExperiment(window, 'blurry-placeholder', true, true);
test/functional/test-amp-img.js
Outdated
impl.buildCallback(); | ||
impl.layoutCallback(); | ||
impl.firstLayoutCompleted(); | ||
let el = impl.element; | ||
let img = el.firstChild; | ||
expect(img.style.opacity).to.equal('0'); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add an expectation that togglePlaceholder was not called here.
test/functional/test-amp-img.js
Outdated
el = impl.element; | ||
img = el.firstChild; | ||
expect(impl.togglePlaceholder).to.have.been.calledWith(false); | ||
impl.unlayoutCallback(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why call unlayoutCallback
here?
test/functional/test-amp-img.js
Outdated
el.appendChild(img); | ||
el.getResources = () => Services.resourcesForDoc(document); | ||
const impl = new AmpImg(el); | ||
impl.layoutWidth_ = 200; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Stub getLayoutWidth()
instead.
sandbox.stub(impl, 'getLayoutWidth').returns(200);
.i-amphtml-blur { | ||
filter: blur(20px) !important; | ||
transform: scale(1.1) !important; | ||
transition: opacity 0.3s cubic-bezier(0.0, 0.0, 0.2, 1) !important; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We decided to use this cubic-bezier as it is what is suggested for easing elements in per the material design doc:
https://material.io/design/motion/speed.html#easing
If one of you with merge access can help me merge this, that would be greatly appreciated! (Assuming everything looks ready - otherwise PTAL again) |
test/functional/test-amp-img.js
Outdated
el = impl.element; | ||
img = el.firstChild; | ||
expect(impl.togglePlaceholder).to.have.been.calledWith(false); | ||
impl.unlayoutCallback(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missed comment from earlier: Why is unlayoutCallback
called here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh sorry! I just saw it in the aftereach of other tests, so I included it in my aftereach method, and then I took out the afterEach because I only had one test method after deleting the other two, so I included the code here. I'll take it out though!
…pproject#17132) * Redid blur detection changes with updated amp-img file * Fixed stylistic issues * Lint errors * Got the animation of the fading in of the rendered image * Fixed wrong variable name * Fix small changes * fixed lint errors * Added tests * Fixed lint errors * Re-add accidentally deleted line * Fixed some of the requested changes. Animation changes are a WIP * Consolidated changes but need to reconfigure tests * Took out comments and debugging code * Rewrote tests to reflect implementation change * Consolidated changes further and redid tests * Fixed small style changes * Fixed tests and small fixes * Fixed javadoc * Fixed style things * Changed bundle size * Took out unnecessary unlayoutCallback() call