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
相关产品推荐
相关产品推荐

