You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Streamlit+Javascript实现点击显图与外部点击关闭功能求助

解决Streamlit集成JS实现点击显示/外部关闭图片弹窗的问题

问题根源分析

  • Streamlit是会话驱动框架,按钮点击会触发整个脚本重新执行,原代码中is_clicked仅在点击瞬间为True,脚本重跑后状态丢失,导致弹窗一闪而过。
  • 直接绑定document的点击事件会和Streamlit自身的页面交互逻辑冲突,且DOM元素会随Streamlit重渲染被重置。
  • 重复的关闭逻辑(遮罩层点击+全局点击监听)容易引发异常触发。

修正后的完整代码

import streamlit as st
from PIL import Image
import base64
from io import BytesIO

def main():
    st.title("Sliding Image App")

    # 初始化会话状态,记录弹窗是否显示
    if "show_popup" not in st.session_state:
        st.session_state.show_popup = False

    # 点击按钮切换弹窗状态
    if st.button("🖼️ Show Image", key="image_button"):
        st.session_state.show_popup = not st.session_state.show_popup

    # 根据会话状态决定是否显示弹窗
    if st.session_state.show_popup:
        show_image()

def show_image():
    # 替换为你的图片路径
    image_path = r"C:\Users\jdslo\OneDrive\Bureau\website\langor_seq06_Sh250.png"

    original_image = Image.open(image_path)
    target_size = (1920, 1080)
    original_image.thumbnail(target_size)

    # 图片转base64
    buffered = BytesIO()
    original_image.save(buffered, format="PNG")
    image_base64 = base64.b64encode(buffered.getvalue()).decode("utf-8")

    max_image_height = "70vh"

    html_code = f"""
        <style>
            .popup {{
                display: none;
                position: fixed;
                top: 0;
                left: 0;
                width: 100vw;
                height: 100vh;
                z-index: 9999;
            }}
            .popup.open {{
                display: block;
            }}
            .blocker {{
                position: absolute;
                top: 0;
                left: 0;
                bottom: 0;
                right: 0;
                background: rgba(0, 0, 0, 0.5);
            }}
            .popup .contents {{
                border: 1px solid #ccc;
                border-radius: 5px;
                width: 80vw;
                height: {max_image_height};
                display: flex;
                align-items: center;
                justify-content: center;
                background: #FFF;
                position: absolute;
                top: 50%;
                left: 50%;
                transform: translate(-50%, -50%);
                animation: slideIn 0.5s forwards;
            }}
            .popup .contents img {{
                max-height: {max_image_height};
                max-width: 100%;
                object-fit: contain;
            }}

            @keyframes slideIn {{
                from {{ transform: translate(-50%, -50%) scale(0.8); opacity: 0; }}
                to {{ transform: translate(-50%, -50%) scale(1); opacity: 1; }}
            }}
            @keyframes slideOut {{
                from {{ transform: translate(-50%, -50%) scale(1); opacity: 1; }}
                to {{ transform: translate(-50%, -50%) scale(0.8); opacity: 0; }}
            }}
        </style>

        <div class="popup open" id="sliding-image-container">
            <div class="blocker" onclick="closePopup()"></div>
            <div class="contents" onclick="event.stopPropagation()">
                <img src="data:image/png;base64,{image_base64}" alt="Sliding Image">
            </div>
        </div>

        <script>
            function closePopup() {{
                const popup = document.getElementById('sliding-image-container');
                popup.style.animation = 'slideOut 0.5s forwards';
                setTimeout(() => {{
                    // 通过Streamlit的会话状态API更新状态
                    window.parent.document.dispatchEvent(new CustomEvent('streamlit:setSessionState', {{
                        detail: {{ show_popup: false }}
                    }}));
                }}, 500);
            }}

            // 监听外部点击(仅通过遮罩层触发,避免和Streamlit冲突)
            document.getElementById('sliding-image-container').addEventListener('click', function(e) {{
                if (e.target.classList.contains('blocker')) {{
                    closePopup();
                }}
            }});
        </script>
    """

    st.markdown(html_code, unsafe_allow_html=True)

if __name__ == "__main__":
    main()

关键修改点说明

  • 会话状态管理:用st.session_state.show_popup持久化弹窗显示状态,避免Streamlit重渲染后状态丢失。
  • 简化关闭逻辑:仅通过遮罩层(.blocker)的点击事件触发关闭,移除全局document监听,避免和Streamlit自身交互冲突。
  • 动画优化:新增淡出动画,同时通过Streamlit的自定义事件streamlit:setSessionState同步更新会话状态,确保弹窗关闭后按钮状态一致。
  • DOM结构调整:将弹窗容器设为全屏fixed布局,避免层级和定位问题。

内容的提问来源于stack exchange,提问作者Léo Chabrier

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 05:35:13