目录
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);
});
感谢阅读!在您离开之前,不妨留下您的足迹:
👍 点个赞,让我知道您喜欢这篇文章!
📝 有任何想法、建议或问题?欢迎在下方评论区分享您的想法!
🔁 分享给您的朋友们,让更多人受益!
💌 喜欢我的内容?立即订阅我的博客,获取更多精彩内容!
谢谢您的支持与参与,让我们一起创造更好的内容体验!