-
Notifications
You must be signed in to change notification settings - Fork 8.3k
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
[Files] Simplify image component implementation and usage #145347
[Files] Simplify image component implementation and usage #145347
Conversation
ctx.putImageData(imageData, 0, 0); | ||
|
||
return new Promise((resolve) => { | ||
// On this point canvas contains a downsized blurred image |
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 is the part I don't like but couldn't find the workaround.
To make the blurred image sizing behave just like the original image we must have the blurred image stretched back to the original size. The problem is that I couldn't find a way to do this synchronously, so now there is a moment in time where image component doesn't render nor the blurred version, nor the src version
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.
Maybe add this as a comment in the code?
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.
Maybe something like below. image
param can be another canvas.
ctx2.drawImage(ctx, 0, 0, blurWidth, blurHeight, 0, 0, width, height);
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.
Actually, can we just return canvas.toDataURL()
once we have the first canvas set up and skip the async image loading step? 😄
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.
@vadimkibana, the idea with the second canvas works. thanks
I was trying it before like this:
ctx2.drawImage(ctx1, 0, 0, width, height);
But it actually should be like this (drawImage can receive canvas element, not ctx):
ctx2.drawImage(canvas1, 0, 0, width, height);
Actually, can we just return canvas.toDataURL() once we have the first canvas set up and skip the async image loading step?
I needed the second step (the image loading or the second canvas) to resize the blurred image back to the original size, so it behaves the same as the original image
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.
Code changes LGTM.
const { isVisible, ref: observerRef } = useViewportObserver({ onFirstVisible }); | ||
export const Image = ({ src, url, alt, onLoad, onError, meta, ...rest }: Props) => { | ||
const imageSrc = (src || url)!; // <EuiImage/> allows to use either `src` or `url` | ||
const [currentImageSrc, onBlurHashLoaded] = useCurrentImageSrc(imageSrc, meta); |
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.
Just an idea, I'm curious if we could have it in the future something like:
const src = useObservable(file.src$);
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.
E.g. the Content Management Cache could return a content item file
object:
const ContentThumbnail = ({ id }) => {
const { cache } = useContent();
const file = cache.get(id);
// but, then what is next: (1) there could be a generic system for content items to return an associated image:
const { src, width, height } = useObservable(file.img$);
// or, (2) some content items could register "custom" metadata in the registry
const { src, width, height } = useObservable(file.custom.img$);
return <img src={src} ... />
};
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 is, any content item could specify an associated image, but also for some content items, like dashboard we could create a screenshot to use as a thumbnail.
ctx.putImageData(imageData, 0, 0); | ||
|
||
return new Promise((resolve) => { | ||
// On this point canvas contains a downsized blurred image |
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.
Maybe something like below. image
param can be another canvas.
ctx2.drawImage(ctx, 0, 0, blurWidth, blurHeight, 0, 0, width, height);
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.
Great work @Dosant , I tested this locally and it is working as intended.
It's really cool that we can get rid of the viewport observer. Could we make the default for the Image
component that it uses lazy loading (i.e., by default set it, and accept a special value false
to set it to undefined
?
(will finish my review later today)
(Story) => { | ||
React.useLayoutEffect(() => { | ||
// @ts-ignore | ||
window.__image_stories_simulate_slow_load = 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.
Nice hack 😄
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.
Generally adding hack code into production for stories/tests should be avoided, but I see why you have done it in this case.
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.
Agree that such hacks must be avoided.
I thought of also using process.env.NODE_ENV !== 'production
but looks like env is production is storybook :(
ctx.putImageData(imageData, 0, 0); | ||
|
||
return new Promise((resolve) => { | ||
// On this point canvas contains a downsized blurred image |
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.
Maybe add this as a comment in the code?
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.
Discussed offline and left a few comments in the code. Most notable:
- Would be nice to address readability comments (up to you if you want to keep hook outside component though)
- Can we return
canvas.getDataUrl()
from the first canvas and skip the async Image loading step?
Summary of changes, we are trading the following:
- Only show the blurhash when the image is taking a long time to load
- Fade the blurhash away when the image is actually ready (UX effect)
For a simplified implementation that still does most of what the initial implementation did. Great work man 👏🏻
ctx.putImageData(imageData, 0, 0); | ||
|
||
return new Promise((resolve) => { | ||
// On this point canvas contains a downsized blurred image |
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.
Actually, can we just return canvas.toDataURL()
once we have the first canvas set up and skip the async image loading step? 😄
@elasticmachine merge upstream |
@elasticmachine merge upstream |
@elasticmachine merge upstream |
💚 Build Succeeded
Metrics [docs]Module Count
Public APIs missing comments
Async chunks
Page load bundle
Unknown metric groupsAPI count
ESLint disabled in files
ESLint disabled line counts
Total ESLint disabled count
History
To update your PR or re-run it, just comment with: |
…146159) ## Summary Follow up to #145347 Needed for #81345 Partially fixes #145567 Two things: - [x] Handle errors related to blurhash loading. Make sure that the original image is loaded if blurhash is failed to generate src or failed to load - [x] Make blurhash appear after the delay. This is needed for better UX when the original image loads fast. Implemented using `css` animation where first part of the animation is a static state with `opacity: 0,` and then the image is revealed in the end. If the original image loads faster, then it appears instantaneously without the delay
Needed for #81345
Summary
While working on the Image Embeddable I've struggle with using the image component, especially making the blur hash part work as I wanted. The main problems were caused by a custom container around the image which had custom styling - I had to properly style it separately to fit my use-case. The other problem was the BlurHash as since it was implemented as a separate image with custom positioning, I couldn't easily position it exactly the same as I positioned the original image.
To make Image component easier to use I suggest the following changes. I also wanted to make the component thinner.
blurDelayExpired
. This isn't great, but I think we can live with that considering the simplicity of the new approach. I also reduced the blur box size so that it calculates a bit more faster, as it would have to do this for every image now.EuiImage
(like predefined set of sizes). Instead of keeping the partial implementation with some code copy paste, I simply suggest we useEuiImage
underneath. This make our image a tiny wrapper aroundEuiImage
which also supports blurhash.