d3.js是一个可以绘制svg,附带方便的选择器和提供各类dom操作api的库。近期碰到这么一个需求,需要在一张svg绘制的几千个点(circle)的图内,实现点击左侧菜单,返回符合筛选条件的焦点移动到屏幕中间,例图如下:
这个需求有点类似于百度地图,搜索出对应结果,点击后屏幕自动移到中间,让使用者清楚的知道该点在整张图的位置,增强用户体验。接到这个需求的时候,我分析了一下页面的dom结构,可以看出整个地图的缩放和平移控制是由g元素的transform来控制的,见dom结构树:
每一次地图的用户拖拽平移缩放操作,都将影响g的translate的值(x,y)scale(k)。于是我想到每一次定位时,直接操作g.attr('transform', translate)即可。但是问题出现了,由于地图g的zoom不是直接创建,通过指定初始化svg.call(zoom.transorm, translate)来建立的zoom,直接操作g的transform,地图会无法识别直接消失。。。于是我把地图zoom存放在一个变量中,传参数的形式下去,代码如下:
let svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
svg.call(zoom).call(zoom.transform, zoomInit(_this.svgWidth, _this.svgHeight));
let g = svg.selectAll("g");
g.remove();
g = svg.append("g").style("z-index", 2);
let zoom = d3
.zoom()
.scaleExtent([0.1, 5])
.on("zoom", zoomed);
this.zoomMap = zoom;
let _this = this;
function zoomed () {
let transform = d3.event.transform;
g.attr("transform", transform);
// 这里做个时延提高性能
clearTimeout(_this.timer);
_this.timer = setTimeout(() => {
if (_this.imagesData.length === 0) {
_this.appendCircle(k); // 添加circle 和path的方法
_this.setZoomCenter(_this.Data, transform.k); // 设置mark并定位的方法
}
}, 300);
}
function zoomInit(width, height) {
return d3.zoomIdentity
.translate(width / 2, height / 2)
.scale(k);
}
于是在vue的内存变量中可以得到zoom的方法,是个d3地图的回调function,通过操作zoom.transform即可操作图层,即
g.call(this.zoomMap.transform, d3.zoomIdentity.translate(width, height).scale(k));
但是这里又遇到了一个坑,图层是搜索完平移了,但是再进行别的操作的时候,图层又回归到初始状态,我上d3的官网上查看,发现是zoomed方法里d3.event.tranform在作祟,而且无法直接修改这个值!
由于直接修改这个值行不通,我到stackoverflow上看到有人说svg会缓存d3初始化时的所有状态,于是我想到一个点子,不仅把g的transform改了,也把svg的zoom信息也给初始化!核心代码如下:
setZoomCenter(val, k) {
// svgwidth 和height是当前屏幕宽高,k是缩放系数。这样可以适配任何端
let svg = d3.select('#xxxx');
let g = svg.selectAll("g");
let cx = val[0].x;
let cy = val[0].y;
// cx,cy是焦点的坐标,这里得出一个方程用来算出zoom的平移距离,因为初始焦点是当前屏幕的横纵坐标的一半,所以根据 m = svgWidth - k * cx, n = svgHeight - k * cy两个方程可以计算出平移距离
let width = this.svgWidth / 2 - k * cx;
let height = this.svgHeight / 2 - k * cy;
g.call(map.transform, d3.zoomIdentity.translate(width, height).scale(k));
svg.call(map.transform, d3.zoomIdentity.translate(width, height).scale(k));
}
这里不仅重置了g,还重置了父元素的transform, 结合到业务中,元素可以在不同的地图缩放等级中进行焦点定位,也可以顺利进行别的操作,至此大功告成。
参考链接:
https://mefelixwang.gitbooks.io/d3-v4-api/api/Zooming.html#zoomtransform-tostring
https://stackoverflow.com/questions/45570145/d3-v4-reset-zoom-state