It Works! Floating Image Tooltips on MouseOver Event

Hey there,
I’m currently working on a search engine using Streamlit and wanted to find a way to display a floating image tooltip for each search result on mouse over.

There didn’t seem to be a currently accepted method on how to do this, but I wasn’t happy with being told that it can’t be done and couldn’t put it to rest :slight_smile:

After a bunch of head scratching, I was able to get it working using pure CSS and HTML to generate a hover event for a single div, displaying a floating image tooltip on mouseover - hooray!

However, while I got this to work relatively easily using img tags, it occurred to me that loading 1000 images at the same time as receiving the search results absolutely tanks the page performance and the UX and wasn’t going to be useful.

With a bit more experimentation, and after getting very poor results using the HTML/CSS tag ‘lazy-loading=true’ (see: it doesn’t seem to work at all in Streamlit?) I figured out a different approach to dynamically load individual image URLs into the browser cache at the time of the mouse over event.

By using python f-strings to generate unique CSS code for each image URL in my list and then using the image URL as the background-image for the container div on hover.

No Javascript necessary & it works seamlessly without any Streamlit quirks you might expect - no need to play around with the session_cache or refresh the page for each url etc.

Hope it’s useful for others - enjoy

import streamlit as st

# Function to display the image on hover
def display_image_on_hover(image_url, i):
    # Generate unique class names for each image
    hover_class = f'hoverable_{i}'
    tooltip_class = f'tooltip_{i}'
    image_popup_class = f'image-popup_{i}'

    # Define the unique CSS for each image
    hover_css = f'''
        .{hover_class} {{
            position: relative;
            display: inline-block;
            cursor: pointer;
        }}
        .{hover_class} .{tooltip_class} {{
            opacity: 0;
            position: absolute;
            bottom: 100%;
            left: 50%;
            transform: translateX(-50%);
            transition: opacity 0.5s;
            background-color: rgba(0, 0, 0, 0.8);
            color: #fff;
            padding: 4px;
            border-radius: 4px;
            text-align: center;
            white-space: nowrap;
        }}
        .{hover_class}:hover .{tooltip_class} {{
            opacity: 1;
        }}
        .{image_popup_class} {{
            position: absolute;
            display: none;
            background-image: none;
            width: 200px;
            height: 200px;
        }}
        .{hover_class}:hover .{image_popup_class} {{
            display: block;
            background-image: url({image_url});
            background-size: cover;
            z-index: 999;
        }}
    '''
    tooltip_css = f"<style>{hover_css}</style>"

    # Define the html for each image
    image_hover = f'''
        <div class="{hover_class}">
            <a href="{image_url}">{image_url}</a>
            <div class="{tooltip_class}">Image {i}</div>
            <div class="{image_popup_class}"></div>
        </div>
    '''
    
    # Write the dynamic HTML and CSS to the content container
    st.markdown(f'<p>{image_hover}{tooltip_css}</p>', unsafe_allow_html=True)

# Initialize Streamlit app
st.title("Floating Image Tooltips on MouseOver")

# A list of image urls
url_list = [
    "https://everydayswag.org/images/misc/ableton-sampler-vol-env.png",
    "https://everydayswag.org/images/email.png"
]

# Create a container for the content that triggers the tooltip on mouseover
with st.expander("⛓", expanded=True):
    # Generate the dynamic HTML and CSS for each URL in the list
    for i, url in enumerate(url_list):
        display_image_on_hover(url, i)

Edit: deployed a demo to the cloud - https://image-hover-tooltips.streamlit.app/

7 Likes

Thank you very much. I have been searching for the same function, but I am struggling to clearly describe its function. :laughing:

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.