yewstack / yew

Rust / Wasm framework for creating reliable and efficient web applications
https://yew.rs
Apache License 2.0
30.78k stars 1.43k forks source link

How to react to `load` events for images #1074

Closed squarfed closed 4 years ago

squarfed commented 4 years ago

I would like to implement some form of progressive loading for images, and I need to react to load events that signals when an image has been fully downloaded. Any hints how to do that?

webbrandon commented 4 years ago

I have a similar need which I had some direction on in #1169. I wanted to take his advice and look at adding support to get metadata(exif) for image.

Stigjb commented 4 years ago

I did this the other day:

fn rendered(&mut self, first_render: bool) {
    // ...
    // Initialize spritesheet
    let link = self.link.clone();
    let onload = Closure::wrap(Box::new(move || {
        link.send_message(Msg::SpritesLoaded);
    }) as Box<dyn Fn()>);
    self.spritesheet
        .set_onload(Some(onload.as_ref().unchecked_ref()));
    self.spritesheet.set_src("sprites.png");
    onload.forget();

In this case, self.spritesheet was a HtmlImageElement created outside of view, and not attached to the DOM. And I didn't come up with the code myself, and cannot really say if it's safe or sensible

jstarry commented 4 years ago

Thanks for adding that snippet @Stigjb, that's a decent workaround for now!

Let's track work on supporting onload here: https://github.com/yewstack/yew/issues/1232

Feel free to re-open this issue if #1232 doesn't fully cover the question

Enigo commented 12 months ago

Did you manage to have it working @squarfed? I was able to figure it out for one image, but can't seem to find a way to do it with a collection of images. Consider the following snippet:

    let image_loaded = use_state(|| false);
    let collections = collections.iter().map(|collection| {
        let image_loaded = image_loaded.clone();
        let onload = {
            let image_loaded = image_loaded.clone();
            Callback::from(move |_| {
                image_loaded.set(true);
            })
        };
        html! {
            <div>
                <img src={collection.collection_image_url.clone()} onload={onload.clone()}/>
               if {!*image_loaded} {
                    <div class="loading-overlay">
                        <div class="loading-animation"></div>
                    </div>
                }
            </div>
        }
    }).collect::<Html>();

This snippet will stop showing the loading-overlay for all images once the first image is loaded. And I cannot use the hooks inside the map function. So, a bit stuck here, any input is greatly appreciated!

Enigo commented 12 months ago

A quick update here. I managed to make it work with web_sys help and with no hooks at all

use web_sys::{Element, Node};
...
    let collections = collections.iter().map(|collection| {
        let div: Element = web_sys::window().expect("Can't find window").document().expect("Can't find document").create_element("div").unwrap();
        div.set_attribute("class", "loading-overlay").unwrap();
        div.set_inner_html("<div class='spinner'></div>");
        let node: Node = div.clone().into();
        html! {
                <div>
                    <img src={collection.collection_image_url.clone()}
                        onload={move |_| div.set_attribute("style", "opacity: 0").unwrap()}/>
                    { Html::VRef(node) }
                </div>
        }
    }).collect::<Html>();

and following CSS:

.spinner {
    width: 60px;
    height: 60px;
    border: 6px solid white;
    border-top: 6px solid #212529;
    border-radius: 50%;
    animation: spin 1s linear infinite;
}

@keyframes spin {
    0% { transform: rotate(0deg); }
    100% { transform: rotate(360deg); }
}

.loading-overlay {
    position: absolute;
    top: 0;
    width: 250px;
    height: 250px;
    background-color: rgba(255, 255, 255, 0.8);
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 1;
    transition: opacity 0.3s ease;
}

well, it is kinda ugly, but it works!