D3JS 画布缩放与平移(修改d3默认事件(进阶):按住轮滑为平移,滚动轮滑为缩放)

目录

效果图

实现逻辑

1、创建一个SVG容器:

2、创建一个主分组:

3、添加背景:

4、定义缩放和平移行为:

5、应用缩放和平移行为:

6、定义鼠标事件:

7、事件监听

完整代码


 D3.js绘图工具系列文章总提纲:【传送门】

默认事件:鼠标左键为平移,滚动轮滑为缩放:【传送门】

效果图

实现逻辑

通过修改 mousedown、mousemove与mouseup事件实现。

1、创建一个SVG容器:

使用d3.js创建一个SVG容器(h5代码中须有相关id标签),用于呈现图表和图形;

let svg = reactive({});

// 生成画布的dom节点id为d3Canvas;须在html代码中添加
svg = d3
    .select('#d3Canvas')
    .append("svg")
    .attr("id", "svg")
    .attr("width", 500)
    .attr("height", 500)
    .attr("viewBox", "0 0 500 500");

2、创建一个主分组:

使用d3.js对所创建的SVG容器,添加一个g标签,用于控制整体缩放;不将事件直接绑定于svg上,是因为会出现bug:画面呈现出一闪一闪的,解决方案是将所有元素使用g标签进行包裹。

let g = reactive({});

g = svg.append("g").attr("id", "svg-grid");

3、添加背景:

用于让拖拽缩放效果更明显;

g.insert("rect", ":first-child")
    .attr("width", "100%")
    .attr("height", "100%")
    .attr("fill", "url(#grid)");

// 使用网格样式填充 SVG 元素背景
var pattern = svg.insert("defs", ":first-child")
    .append("pattern")
    .attr("id", "grid")
    .attr("width", 20)
    .attr("height", 20)
    .attr("patternUnits", "userSpaceOnUse");

// 添加网格线
pattern.append("path")
    .attr("d", "M 20 0 L 0 0 0 20")
    .attr("stroke", "lightgray")
    .attr("stroke-width", 0.5)
    .attr("fill", "none");

4、定义缩放和平移行为:

使用d3.zoom()函数创建一个缩放和平移行为,可以通过设置缩放比例和平移偏移量来控制缩放和平移的效果;

function zoomed(event, svg) {
    // 对g进行偏移赋值
    g.attr("transform", event.transform);
}
var zoom = d3
    .zoom()
    .scaleExtent([0.3, 10])
    .on("zoom", (event) => zoomed(event));

5、应用缩放和平移行为:

将缩放和平移行为应用到SVG容器上,使得图表可以响应鼠标事件。
注:添加阻止鼠标落下默认事件。

svg.call(zoom).on("mousedown.zoom", null);

6、定义鼠标事件:

参数定义:

let isPanning = false;
let startX = 0, startY = 0;

鼠标落下事件(mousedown):

function panMouseDown(event) {
    if (event.button !== 1) return; // 中键按下
    console.log('中键按下');
    isPanning = true;
    [startX, startY] = d3.pointer(event);
}

鼠标移动事件(mousemove):

function panMouseMove(event) {
    if (isPanning) {
        event.preventDefault();
        const [x, y] = d3.pointer(event);
        const transform = d3.zoomTransform(g.node()); // 获取当前变换

        // 方法一 推荐
        const transformedX = (x - startX) / transform.k;
        const transformedY = (y - startY) / transform.k;
        zoom.translateBy(g, transformedX, transformedY); // 使用偏移量进行平移
        startX = x; // 更新起始位置
        startY = y;
    }
}

方法二为通过 translateTo 方法控制,但是相比于方法一,存在卡顿现象:

// 方法二
if (startX!== x || startY!== y) {
    // 使用偏移量进行平移
    zoom.translateTo(g, startX, startY, [x, y]); 
}

 鼠标抬起事件(mouseup):

function panMouseUp() {
    if (isPanning) {
        isPanning = false;
    }
}

7、事件监听

svg.on("mousedown", function (event) {
    panMouseDown(event);
});
svg.on("mousemove", function (event) {
    panMouseMove(event);
});
svg.on("mouseup", function (event) {
    panMouseUp(event);
});

完整代码

let svg = reactive({});

// 生成画布的dom节点id为d3Canvas;须在html代码中添加
svg = d3
    .select('#d3Canvas')
    .append("svg")
    .attr("id", "svg")
    .attr("width", 500)
    .attr("height", 500)
    .attr("viewBox", "0 0 500 500");

let g = reactive({});

g = svg.append("g").attr("id", "svg-grid");

g.insert("rect", ":first-child")
    .attr("width", "100%")
    .attr("height", "100%")
    .attr("fill", "url(#grid)");

// 使用网格样式填充 SVG 元素背景
var pattern = svg.insert("defs", ":first-child")
    .append("pattern")
    .attr("id", "grid")
    .attr("width", 20)
    .attr("height", 20)
    .attr("patternUnits", "userSpaceOnUse");

// 添加网格线
pattern.append("path")
    .attr("d", "M 20 0 L 0 0 0 20")
    .attr("stroke", "lightgray")
    .attr("stroke-width", 0.5)
    .attr("fill", "none");

function zoomed(event, svg) {
    // 对g进行偏移赋值
    g.attr("transform", event.transform);
}

var zoom = d3
    .zoom()
    .scaleExtent([0.3, 10])
    .on("zoom", (event) => zoomed(event));

g.call(zoom).on("mousedown.zoom", null);

let isPanning = false;
let startX = 0, startY = 0;

function panMouseDown(event) {
    if (event.button !== 1) return; // 中键按下
    console.log('中键按下');
    isPanning = true;
    [startX, startY] = d3.pointer(event);
}

function panMouseMove(event) {
    if (isPanning) {
        event.preventDefault();
        const [x, y] = d3.pointer(event);
        const transform = d3.zoomTransform(g.node()); // 获取当前变换

        // 方法一 推荐
        const transformedX = (x - startX) / transform.k;
        const transformedY = (y - startY) / transform.k;
        zoom.translateBy(g, transformedX, transformedY); // 使用偏移量进行平移
        startX = x; // 更新起始位置
        startY = y;
    }
}

function panMouseUp() {
    if (isPanning) {
        isPanning = false;
    }
}

svg.on("mousedown", function (event) {
    panMouseDown(event);
});
svg.on("mousemove", function (event) {
    panMouseMove(event);
});
svg.on("mouseup", function (event) {
    panMouseUp(event);
});

感谢阅读!在您离开之前,不妨留下您的足迹:

👍 点个赞,让我知道您喜欢这篇文章!

📝 有任何想法、建议或问题?欢迎在下方评论区分享您的想法!

🔁 分享给您的朋友们,让更多人受益!

💌 喜欢我的内容?立即订阅我的博客,获取更多精彩内容!

谢谢您的支持与参与,让我们一起创造更好的内容体验!

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值