Dash调用Javascript代码实现悬浮框拖拽功能

在 Python 中使用 Dash 库编写前端页面有时需要引入 JavaScript 代码实现纯 Dash 库无法实现的特殊功能,比如监听浏览器页面中鼠标的位置。

直接使用 html.Script 能实现加载 JS 文件,但无法直接通过它执行 JavaScript 代码;额外引入 dash-extensions 库可以使用其封装好的事件监听函数 EventListener 监听鼠标位置,但是该库资料较少,容易错误使用,bug 排查较为困难。

Dash 官方文档中推荐以 clientside_callback 客户端回调的方式引入 Javascript 代码使用,因此本文采取此方式引入 Javascript 代码实现带关闭按钮的悬浮框拖拽功能,JS 代码与 Python 代码分离便于管理维护。

script.js
/*--------窗口拖动功能--------*/
// 点击窗口触发拖拽
window.dash_clientside = Object.assign({}, window.dash_clientside, {
    clientside: {
        container_drag_function:
            function (n_clicks, currentPos) {
                //const handle = document.getElementById('drag-handle');  // 只拖动头部
                const dialog = document.getElementById('draggable-dialog');
                // 可视窗口的大小
                let winWid = document.documentElement.clientWidth || document.body.clientWidth;
                let winHei = document.documentElement.clientHeight || document.body.clientHeight;
                // 可视窗口大小减掉所需移动的窗口大小,得出这个窗口所能达到的最边缘值,中间值为0时完全贴合窗口边缘
                winWid = winWid - 0 - dialog.offsetWidth
                winHei = winHei - 0 - dialog.offsetHeight

                if (!window.dragSetup) {
                    window.dragSetup = true;
                    let isDragging = false;
                    let offsetX, offsetY;

                    dialog.addEventListener('mousedown', function (e) {
                        isDragging = true;
                        offsetX = e.clientX - dialog.offsetLeft;
                        offsetY = e.clientY - dialog.offsetTop;
                        e.preventDefault();
                    });

                    document.addEventListener('mousemove', function (e) {
                        if (isDragging) {
                            // 不要用const,会让拖拽变卡,并且下面if判断中的newX的值会无法更新
                            let newX = e.clientX - offsetX;
                            let newY = e.clientY - offsetY;
                            if (newX <= 0) {
                                newX = 1
                            }
                            if (newY <= 0) {
                                newY = 1
                            }
                            if (newX > winWid) {
                                newX = winWid
                            }
                            if (newY > winHei) {
                                newY = winHei
                            }
                            dialog.style.left = newX + 'px';
                            dialog.style.top = newY + 'px';
                            return {'x': newX, 'y': newY};
                        }
                        return dash_clientside.no_update;
                    });

                    document.addEventListener('mouseup', function () {
                        isDragging = false;
                    });
                }
                return currentPos;
            }
    }
});
/*--------窗口拖动功能--------*/
dialog.py
import dash
from dash import html, Input, Output, callback, dcc, ClientsideFunction

app = dash.Dash(__name__)

app.layout = html.Div([
    html.Div(
        id="draggable-dialog",
        children=[
            html.Div(
                children=[
                    html.Span("可拖动对话框", style={'flex': 1}),
                    html.Button('×', id='close-btn', n_clicks=0,
                                style={'background': 'none', 'border': 'none',
                                       'font-size': '20px', 'cursor': 'pointer', 'float': 'right'})
                ],
                id="drag-handle",
                style={'padding': '10px', 'background': '#4285f4',
                       'color': 'white', 'cursor': 'move'}),
            html.Div("对话框内容区域",
                     style={'padding': '20px', 'border': '1px solid #ddd'})
        ],
        style={
            'position': 'fixed',
            'width': '300px',
            'border-radius': '5px',
            'box-shadow': '0 4px 8px rgba(0,0,0,0.1)',
            'z-index': 1000
        }
    ),
    # 此处设置的x、y初始值对容器位置没有影响
    dcc.Store(id='position-store', data={'x': 0, 'y': 0})
])


# 按钮对话框关闭功能
@callback(
    Output('draggable-dialog', 'style', allow_duplicate=True),
    Input('close-btn', 'n_clicks'),
    prevent_initial_call=True
)
def close_dialog(n_clicks):
    if n_clicks > 0:
        return {'display': 'none'}
    return dash.no_update


# 通过在定义的clientside_callback客户端回调函数中引入鼠标位置监听的javascript代码
# 容器拖拽功能的回调直接在浏览器中运行,以减少服务器端开销成本
app.clientside_callback(
    # 调用assets文件夹中的js代码
    ClientsideFunction(
        namespace='clientside',
        function_name='container_drag_function'
    ),
    Output('position-store', 'data'),
    Input('draggable-dialog', 'n_clicks'),
    Input('position-store', 'data'),
    prevent_initial_call=True
)

if __name__ == "__main__":
    app.run_server(debug=True)

:script.js 文件放置在assets文件夹中,dialog.py 文件与assets文件夹在同一目录下。

效果图:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值