【趟坑记录】d3.zoom()的正确使用姿势 @d3.v7

【趟坑记录】d3.zoom()的正确使用姿势 @d3.v7

文章目录

  • 【趟坑记录】`d3.zoom()`的正确使用姿势 @d3.v7
      • 问题重现
      • 原因分析
      • 解决方案
        • 放缩平移写法
        • 特殊修改`'transform'`函数的写法
      • 总结

在开发一个D3应用的时候遇到了一个 zoom相关的问题,记录解决思路与方案

问题重现

最近在开发一个D3应用的时候遇到了一个zoom相关的问题,应用里有一个功能叫全景聚焦。我们都知道画布由两个标签组成(见实现autoZoom(),画布自适应放缩并居中@D3.js-v5),最外层的是固定视口<svg>,一般将zoom事件绑定在<svg>上;内层是具体的画布,是一个<g>标签,在<svg>中的放缩与平移操作都作用在<g>上,修改<g>transform属性。这么做是为了避免用户将<svg>元素拖动到窗口之外后丢失拖动焦点,无法将其拖回。而如果使<svg>不动,<g>被拖动,那么拖动焦点就不会丢失,用户将<g>元素移动至视口外后,还能将其拖回来。

我之前习惯这么写拖动平移:

const svg = d3.select('#viewport').attr('width', width).attr('height', height)
const g = svg.append('g').attr('id', 'container').attr('width', width).attr('height', height)svg.call(d3.zoom().on('zoom', (e) => {const transform = `translate(${e.transform.x},${e.transform.y}) scale(${e.transform.k})`g.attr('transform', transform)})
)

在一些业务场景中,往往需要对<g>元素进行特定的平移与放缩。如:自动缩放至视口中央,放大至当前的1.5倍。然而,在其他直接地方修改了<g>‘transform’属性后,如:

const offsetX = 10;
const offsetY = 10;
g.attr('transform',`translate(${offsetX},${offsetY})`

,问题就出现了,如下:

bugreproduce

可以看到,在设置了特定的'transform'后,再进行拖动,会出现瞬移。

原因分析

因为监听的zoom事件是通过e.transform来进行放缩的。而在修改<g>元素的‘transform’属性为一个特定值后,再进行拖动,会从上一次的e.tranform值开始修改,因此会出现错误。

举例说明:

  1. 用户拖动,e.transform的数值修改为了transform_1
  2. 有一个自动放缩函数autoZoom,将<g>'transform'修改为了transform_2
  3. 用户再次进行拖动,<g>'transform'会从transform_1开始修改,因此会出现从transform_2transform_1的瞬移。

解决方案

得知原因之后,解决方案也非常明了。就是在任何需要进行放缩平移的地方,都将transform进行缓存,下一次再需要进行放缩平移操作时,从上一次的transform开始进行更改即可。

一开始我想的解决方案是在每次鼠标拖动时都记录一个偏移量,但是这个偏移量比较难获取,心想d3这么大个库应该不至于用这么蠢的办法,应该有更好用的方案。

查了一下官方的API,发现了一个叫zoomTransform(node)的接口,这个接口传入的是一个HTML node,需要用d3.select(xx).node()来获得,可以获取这个node的放缩数据。官方文档是这么说的:

Internally, an element’s transform is stored as element.__zoom; however, you should use this method rather than accessing it directly. If the given node has no defined transform, returns the transform of the closest ancestor, or if none exists, the identity transformation


在内部,元素的变换存储为 element.__zoom;但是,您应该使用此方法(指的是zoomTransform)而不是直接访问它。如果给定节点没有定义的变换,则返回最近祖先的变换,或者如果不存在,则返回恒等变换。返回的变换表示以下形式的二维变换矩阵(略):

These properties should be considered read-only; instead of mutating a transform, use transform.scale and transform.translate to derive a new transform.


这些属性应被视为只读;使用transform.scale和transform.translate来派生新的变换,而不是改变变换。(下文将介绍如何派生新的变换)

进一步查看了源码,发现在svg.call(zoom)这个操作后,<svg>这个HTML node就会绑上一个__zoom 属性,这个__zoom属性记录的是transform参数,也就是我们对<svg>进行的放缩平移变换。为此我还特定打印了一下,发现确实如此:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YmbofakK-1689904577005)(/Users/zqqcee/Library/Application Support/typora-user-images/image-20230720100136154.png)]

那现在事情就变得很简单了,可以转变一下思路。之前我一直希望能够在autoZoom()之后,获得"zoom"事件的偏移量,使得我能够接着这个'transform'值修改。那么既然我无法获得偏移量,可以尝试在autoZoom()方法中不要直接修改<g>'transform'属性,而去修改<svg>.__zoom值。

放缩平移写法

在一开始时,使用d3.zoom()创建放缩对象zoom,并在任何时刻都使用<svg>call(zoom)修改放缩值。在绑定"zoom"事件时,因为<svg> callzoom,因此任何偏移量都会记录在<svg>,在修改<g>'transform'属性时,可以直接使用d3.zoomTransform(svg.node())来获得<svg>.__zoom来进行应用。

const svg = d3.select('body').append('svg');
const g = svg.append('g');
const zoom = d3.zoom().on('zoom',()=>{g.attr('transform', d3.zoomTransform(svg.node()));
})

特殊修改'transform'函数的写法

这里需要说明一下autoZoom()的写法,假设我们现在已经计算出了'transform'数值transformXtransformYk。现在需要修改<svg>__zoom属性为当前的'transform'数值。

查阅了官方文档,找到了可以使用的API:

  • d3.zoomIdentity。这个API可以创建一个新的'transform':{x:0,y:0,k:1},并允许使用transform.translate(x,y), transform.scale(k)对其进行更改。
  • selection.call(zoom.transform,new_transform);使用这个接口能够将<svg>.__zoom修改为new_transform

综上,代码为:

const new_transform = d3.zoomIdentity.translate(transformX, transformY).scale(k);
d3.select('svg').call(zoom.transform,new_transform);

总结

简而言之,任何对<g>的放缩与平移操作,都需要作用在<svg>上,并且使用<svg>.__zoom()来修改。

完整代码:

//zoom事件绑定
const svg = d3.select('body').append('svg');
const g = svg.append('g');
const zoom = d3.zoom().on('zoom',()=>{g.attr('transform', d3.zoomTransform(svg.node()));
})//需要修改特定transform的函数,以autoZoom为例
const autoZoom = (transformX,transformY,k) =>{const new_transform = d3.zoomIdentity.translate(transformX, transformY).scale(k);d3.select('svg').call(zoom.transform,new_transform);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/7567.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Windows 10, version 22H2 (updated Jul 2023) 中文版、英文版下载

Windows 10, version 22H2 (updated Jul 2023) 中文版、英文版下载 Windows 10 22H2 企业版 arm64 x64 请访问原文链接&#xff1a;https://sysin.org/blog/windows-10/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin.org Window…

FPGA——verilog实现格雷码与二进制的转换

文章目录 一、格雷码简介二、二进制转格雷码三、格雷码转二进制四、仿真 一、格雷码简介 格雷码是一种循环二进制码或者叫作反射二进制码。跨时钟域会产生亚稳态问题&#xff08;CDC问题&#xff09;&#xff1a;从时钟域A过来的信号难以满足时钟域B中触发器的建立时间和保持时…

python实现远程服务器的操作

前言 测试过程中经常会遇到需要将本地的文件上传到远程服务器上&#xff0c;或者需要将服务器上的文件拉到本地就行操作&#xff0c;以前安静经常会用到xftp工具。今天介绍一种python库Paramiko&#xff0c;可以帮助我们通过代码的方式进行完成对远程服务器的上传和下载操作。…

elementui el-table 封装表格

ps: 1.3版本 案例&#xff1a; 完整代码&#xff1a; 可直接复制粘贴&#xff0c;但一定要全看完&#xff01; v-slot"scopeRows" 是vue3的写法&#xff1b; vue2是 slot-scope"scope" <template><!-- 简单表格、多层表头、页码、没有合并列行…

flutter开发实战-Stagger Animation实现水波纹动画

flutter开发实战-实现水波纹动画&#xff0c;使用到了交织动画&#xff0c;实现三个圆逐渐放大与渐变的过程。 一、效果图 二、实现水波纹效果 实现水波纹动画&#xff0c;使用到了交织动画&#xff0c;实现三个圆逐渐放大与渐变的过程。 交织动画 有些时候我们可能会需要一些…

HTTPS连接过程中的中间人攻击

HTTPS连接过程中的中间人攻击 HTTPS连接过程中间人劫持攻击 HTTPS连接过程 https协议就是httpssl/tls协议&#xff0c;如下图所示为其连接过程&#xff1a; HTTPS连接的整个工程如下&#xff1a; https请求&#xff1a;客户端向服务端发送https请求&#xff1b;生成公钥和私…

矩阵置零(力扣)思维 JAVA

给定一个 m x n 的矩阵&#xff0c;如果一个元素为 0 &#xff0c;则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。 输入&#xff1a;matrix [[1,1,1],[1,0,1],[1,1,1]] 输出&#xff1a;[[1,0,1],[0,0,0],[1,0,1]] 输入&#xff1a;matrix [[0,1,2,0],[3,4,5,2],[…

[ 华为云 ] 云计算中Region、VPC、AZ 是什么,他们又是什么关系,应该如何抉择

前几天看到一个问答帖&#xff0c;我回答完了才发现这个帖子居然是去年的也没人回复&#xff0c;其中他问了一些华为云的问题&#xff0c;对于其中的一些概念&#xff0c;这里来总结讲解一下&#xff0c;希望对学习华为云的小伙伴有所帮助。 文章目录 区域&#xff08;Region&a…

计算机基础专升本笔记四 计算机系统

计算机基础专升本笔记四 计算机系统 计算机系统 计算机系统由计算机硬件系统和计算机软件系统 组成。且是按照存储程序的方式工作的。计算机硬件就是由各种电子器件按照一定逻辑连接而成&#xff0c;看的见摸得着&#xff0c;是计算机系统的物质基础&#xff0c;计算机软件系统…

# jellyfin安装设置使用散记

jellyfin安装设置使用散记 文章目录 jellyfin安装设置使用散记0 软件简介1 安装2 视频转码问题2.1 局域网转码情况测试&#xff08;不同网段&#xff09;2.2 局域网jellyfin app默认转码问题解决2.3 外网转码情况测试 3 一些坑4 插件5 最后 0 软件简介 Jellyfin 是一个自由的软…

UDS之11服务

11服务&#xff1a; 功能&#xff1a;控制MCU进行重启&#xff0c;重启分为硬重启和软重启&#xff0c;11服务一般代表软重启&#xff0c;虽然它里面有个子服务是硬件重启&#xff0c;这里需要注意下&#xff1b;硬重启在日常工作中一般代表B重启。命令格式&#xff08;请求&am…

LiveGBS流媒体平台GB/T28181功能-视频直播流媒体平台分屏展示设备树分组树记录上次分屏播放记录

LiveGBS视频直播流媒体平台分屏展示设备树分组树记录上次分屏播放记录 1、分屏展示1.1、单屏1.2、四分屏1.3、九分屏1.4、十六分屏 2、分屏记录3、搭建GB28181视频直播平台 1、分屏展示 LiveGBS分屏页面支持&#xff0c;多画面播放&#xff0c;支持单屏、四分屏、九分屏、十六…

GPT-4 模型详细教程

GPT-4&#xff08;Generative Pretrained Transformer 4&#xff09;是 OpenAI 的最新语言生成模型&#xff0c;其在各类文本生成任务中表现优秀&#xff0c;深受开发者和研究者喜爱。这篇教程将帮助你理解 GPT-4 的基本概念&#xff0c;并向你展示如何使用它来生成文本。 什么…

Java-API简析_java.net.Proxy类(基于 Latest JDK)(浅析源码)

【版权声明】未经博主同意&#xff0c;谢绝转载&#xff01;&#xff08;请尊重原创&#xff0c;博主保留追究权&#xff09; https://blog.csdn.net/m0_69908381/article/details/131881661 出自【进步*于辰的博客】 因为我发现目前&#xff0c;我对Java-API的学习意识比较薄弱…

Linux6.13 Docker LNMP项目搭建

文章目录 计算机系统5G云计算第四章 LINUX Docker LNMP项目搭建一、项目环境1.环境描述2.容器ip地址规划3.任务需求 二、部署过程1.部署构建 nginx 镜像2.部署构建 mysql 镜像3.部署构建 php 镜像4.验证测试 计算机系统 5G云计算 第四章 LINUX Docker LNMP项目搭建 一、项目…

第54步 深度学习图像识别:MLP-Mixer建模(Pytorch)

基于WIN10的64位系统演示 一、写在前面 &#xff08;1&#xff09;MLP-Mixer MLP-Mixer&#xff08;Multilayer Perceptron Mixer&#xff09;是Google在2021年提出的一种新型的视觉模型结构。它的主要特点是完全使用多层感知机&#xff08;MLP&#xff09;来处理图像&#…

3dsmax制作一个小人

文章目录 步骤起阶五官手臂短袖添加头发、头饰BodyPaint软件贴图导入到3dsmax 渲染 步骤 起阶 五官 手臂 短袖 添加头发、头饰 BodyPaint软件贴图 寻找网络贴图&#xff0c;用PS切割&#xff0c;用BodyPaint恢复纹理 导入到3dsmax 渲染

【三维点云处理】顶点、面片、邻接矩阵、邻接距离矩阵以及稀疏存储概念

文章目录 vts和faces基础知识vertices-节点&#xff08;3是点的三维坐标&#xff09;faces-面片&#xff08;3是构成三角形面片的3个点&#xff09; 邻接矩阵邻接距离矩阵&#xff08;NN500&#xff09;稀疏矩阵 vts和faces基础知识 vertices-节点&#xff08;3是点的三维坐标…

设计模式大白话——观察者模式

文章目录 一、概述二、示例三、模式定义四、其他 一、概述 ​ 与其叫他观察者模式&#xff0c;我更愿意叫他叫 订阅-发布模式 &#xff0c;这种模式在我们生活中非常常见&#xff0c;比如&#xff1a;追番了某个电视剧&#xff0c;当电视剧有更新的时候会第一时间通知你。当你…

Fuzz测试:提升自动驾驶安全性

目录 什么是Fuzz测试&#xff1f; 自动驾驶的潜在风险 Fuzz测试&#xff1a;自动驾驶和车联网 Fuzz测试方法有以下几种&#xff1a; 资料获取方法 纵观近百年来汽车制造业的发展历程&#xff0c;产业跨进的每一步背后都有着技术创新作为支撑。汽车技术创新对世界经济、社会…