From 89140713882152f74f4127c49842bd0bca970a87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=AF=B8=E5=B2=B3?= Date: Sun, 10 May 2020 21:23:32 +0800 Subject: [PATCH] fix(g-base): event should work when CSS transform is applied, close #489 --- docs/api/canvas.en.md | 8 +++ docs/api/canvas.zh.md | 8 +++ packages/g-base/package.json | 3 +- packages/g-base/src/abstract/canvas.ts | 49 +++++++++++++++- packages/g-base/src/event/event-contoller.ts | 24 ++------ packages/g-base/src/interfaces.ts | 18 +++++- .../g-canvas/tests/bugs/issue-489-spec.js | 58 +++++++++++++++++++ 7 files changed, 144 insertions(+), 24 deletions(-) create mode 100644 packages/g-canvas/tests/bugs/issue-489-spec.js diff --git a/docs/api/canvas.en.md b/docs/api/canvas.en.md index e1c002803..124558201 100644 --- a/docs/api/canvas.en.md +++ b/docs/api/canvas.en.md @@ -67,6 +67,14 @@ export type Renderer = 'canvas' | 'svg'; - 修改画布大小; +### getPointByEvent(ev: Event): Point + +- 根据事件对象获取画布坐标,返回类型为 `{ x: number, y: number }`; + +### getClientByEvent(ev: Event): Point + +- 根据事件对象获取窗口坐标,返回类型为 `{ x: number, y: number }`; + ### getPointByClient(clientX: number, clientY: number) - 根据窗口坐标,获取对应的画布坐标,返回类型为 `{ x: number, y: number }`; diff --git a/docs/api/canvas.zh.md b/docs/api/canvas.zh.md index 7e8ef4bff..d352a42ec 100644 --- a/docs/api/canvas.zh.md +++ b/docs/api/canvas.zh.md @@ -67,6 +67,14 @@ export type Renderer = 'canvas' | 'svg'; - 修改画布大小; +### getPointByEvent(ev: Event): Point + +- 根据事件对象获取画布坐标,返回类型为 `{ x: number, y: number }`; + +### getClientByEvent(ev: Event): Point + +- 根据事件对象获取窗口坐标,返回类型为 `{ x: number, y: number }`; + ### getPointByClient(clientX: number, clientY: number) - 根据窗口坐标,获取对应的画布坐标,返回类型为 `{ x: number, y: number }`; diff --git a/packages/g-base/package.json b/packages/g-base/package.json index c2836d164..2090fc7f9 100644 --- a/packages/g-base/package.json +++ b/packages/g-base/package.json @@ -60,7 +60,8 @@ "@types/d3-timer": "^1.0.9", "d3-ease": "^1.0.5", "d3-interpolate": "^1.3.2", - "d3-timer": "^1.0.9" + "d3-timer": "^1.0.9", + "detect-browser": "^5.1.0" }, "__npminstall_done": false } diff --git a/packages/g-base/src/abstract/canvas.ts b/packages/g-base/src/abstract/canvas.ts index 247dabdf1..61f9e900a 100644 --- a/packages/g-base/src/abstract/canvas.ts +++ b/packages/g-base/src/abstract/canvas.ts @@ -1,12 +1,16 @@ +import { detect } from 'detect-browser'; import Container from './container'; import { ICanvas } from '../interfaces'; import { CanvasCfg, Point, Renderer, Cursor } from '../types'; -import { isBrowser, isString } from '../util/util'; +import { isBrowser, isNil, isString } from '../util/util'; import Timeline from '../animate/timeline'; import EventController from '../event/event-contoller'; const PX_SUFFIX = 'px'; +const browser = detect(); +const isFirefox = browser && browser.name === 'firefox'; + abstract class Canvas extends Container implements ICanvas { constructor(cfg: CanvasCfg) { super(cfg); @@ -20,6 +24,8 @@ abstract class Canvas extends Container implements ICanvas { const cfg = super.getDefaultCfg(); // set default cursor style for canvas cfg['cursor'] = 'default'; + // CSS transform 目前尚未经过长时间验证,为了避免影响上层业务,默认关闭,上层按需开启 + cfg['supportCSSTransform'] = false; return cfg; } @@ -127,6 +133,47 @@ abstract class Canvas extends Container implements ICanvas { } } + // 实现接口 + getPointByEvent(ev: Event): Point { + const supportCSSTransform = this.get('supportCSSTransform'); + if (supportCSSTransform) { + // For Firefox <= 38 + if (isFirefox && !isNil((ev as any).layerX) && (ev as any).layerX !== (ev as MouseEvent).offsetX) { + return { + x: (ev as any).layerX, + y: (ev as any).layerY, + }; + } + if (!isNil((ev as MouseEvent).offsetX)) { + // For IE6+, Firefox >= 39, Chrome, Safari, Opera + return { + x: (ev as MouseEvent).offsetX, + y: (ev as MouseEvent).offsetY, + }; + } + } + // should calculate by self for other cases, like Safari in ios + // TODO: support CSS transform + const { x: clientX, y: clientY } = this.getClientByEvent(ev); + return this.getPointByClient(clientX, clientY); + } + + // 获取 touch 事件的 clientX 和 clientY 需要单独处理 + getClientByEvent(ev: Event) { + let clientInfo: MouseEvent | Touch = event as MouseEvent; + if ((ev as TouchEvent).touches) { + if (ev.type === 'touchend') { + clientInfo = (ev as TouchEvent).changedTouches[0]; + } else { + clientInfo = (ev as TouchEvent).touches[0]; + } + } + return { + x: clientInfo.clientX, + y: clientInfo.clientY, + }; + } + // 实现接口 getPointByClient(clientX: number, clientY: number): Point { const el = this.get('el'); diff --git a/packages/g-base/src/event/event-contoller.ts b/packages/g-base/src/event/event-contoller.ts index bb8ebbe24..98d548bb5 100644 --- a/packages/g-base/src/event/event-contoller.ts +++ b/packages/g-base/src/event/event-contoller.ts @@ -29,22 +29,6 @@ const EVENTS = [ 'mousewheel', ]; -// 触摸事件的 clientX,clientY 获取有一定差异 -function getClientPoint(event) { - let clientInfo = event; - if (event.touches) { - if (event.type === 'touchend') { - clientInfo = event.changedTouches[0]; - } else { - clientInfo = event.touches[0]; - } - } - return { - clientX: clientInfo.clientX, - clientY: clientInfo.clientY, - }; -} - // 是否有委托事件监听 function hasDelegation(events, type) { for (const key in events) { @@ -169,13 +153,13 @@ class EventController { // 获取事件的当前点的信息 _getPointInfo(ev) { const canvas = this.canvas; - const clientPoint = getClientPoint(ev); - const point = canvas.getPointByClient(clientPoint.clientX, clientPoint.clientY); + const clientPoint = canvas.getClientByEvent(ev); + const point = canvas.getPointByEvent(ev); return { x: point.x, y: point.y, - clientX: clientPoint.clientX, - clientY: clientPoint.clientY, + clientX: clientPoint.x, + clientY: clientPoint.y, }; } diff --git a/packages/g-base/src/interfaces.ts b/packages/g-base/src/interfaces.ts index 14bb243cb..1a544f806 100644 --- a/packages/g-base/src/interfaces.ts +++ b/packages/g-base/src/interfaces.ts @@ -582,10 +582,24 @@ export interface ICanvas extends IContainer { changeSize(width: number, height: number); /** - * 将窗口坐标转变成 canvas 坐标 + * 根据事件对象获取画布坐标 + * @param {Event} ev 事件对象 + * @return {object} 画布坐标 + */ + getPointByEvent(ev: Event): Point; + + /** + * 根据事件对象获取窗口坐标 + * @param {Event} ev 事件对象 + * @return {object} 窗口坐标 + */ + getClientByEvent(ev: Event): Point; + + /** + * 将窗口坐标转变成画布坐标 * @param {number} clientX 窗口 x 坐标 * @param {number} clientY 窗口 y 坐标 - * @return {object} canvas坐标 + * @return {object} 画布坐标 */ getPointByClient(clientX: number, clientY: number): Point; diff --git a/packages/g-canvas/tests/bugs/issue-489-spec.js b/packages/g-canvas/tests/bugs/issue-489-spec.js new file mode 100644 index 000000000..eb2b97c7a --- /dev/null +++ b/packages/g-canvas/tests/bugs/issue-489-spec.js @@ -0,0 +1,58 @@ +// // CSS transform 的单测在 interactive 和 renderer 的模式下表现不一致,因此目前没有想到比较好的方式去测试,先全部注释掉 +// // 已经在 interactive 模式下手动测试通过,CSS transform 下的事件表现是 OK 的 +// const expect = require('chai').expect; +// import Canvas from '../../src/canvas'; +// import { simulateMouseEvent, getClientPoint } from '../util'; + +// const dom = document.createElement('div'); +// document.body.appendChild(dom); +// // 支持嵌套的 CSS transform +// document.body.style.transform = 'translate(200px)'; +// dom.id = 'c1'; +// dom.style.border = '1px solid red'; +// // transform 2d 和 3d 函数都支持 +// dom.style.transform = 'translate(100px, 100px) scale(1.2) rotate3d(1, 1, 1, 45deg)'; + +// describe('#489', () => { +// const canvas = new Canvas({ +// container: dom, +// width: 400, +// height: 400, +// supportCSSTransform: true, +// }); + +// const el = canvas.get('el'); + +// it('event should work when CSS transform is applied', () => { +// const rect = canvas.addShape('rect', { +// attrs: { +// x: 50, +// y: 50, +// width: 50, +// height: 50, +// fill: 'red', +// }, +// }); +// let clickCalled = false; +// rect.on('click', () => { +// clickCalled = true; +// }); +// rect.on('mouseenter', () => { +// rect.attr('fill', 'blue'); +// }); +// rect.on('mouseleave', () => { +// rect.attr('fill', 'red'); +// }); + +// const { clientX, clientY } = getClientPoint(canvas, 75, 75); +// simulateMouseEvent(el, 'mousedown', { +// clientX, +// clientY, +// }); +// simulateMouseEvent(el, 'mouseup', { +// clientX, +// clientY, +// }); +// expect(clickCalled).equals(true); +// }); +// });