From 5d85e7fc7f47a844b90a26ef40d2637b1d7885b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=AF=B8=E5=B2=B3?= Date: Thu, 4 Jun 2020 11:04:59 +0800 Subject: [PATCH] fix(g-canvas): render should work correctly when element is clipped by view, close #494 --- package.json | 4 +- packages/g-canvas/src/canvas.ts | 10 +- packages/g-canvas/src/group.ts | 1 - packages/g-canvas/src/shape/base.ts | 2 + .../g-canvas/tests/bugs/issue-380-spec.js | 2 +- .../g-canvas/tests/bugs/issue-456-spec.js | 2 +- .../g-canvas/tests/bugs/issue-494-spec.js | 120 ++++++++++++++++++ 7 files changed, 134 insertions(+), 7 deletions(-) create mode 100644 packages/g-canvas/tests/bugs/issue-494-spec.js diff --git a/package.json b/package.json index b7386461f..28ead0173 100644 --- a/package.json +++ b/package.json @@ -67,8 +67,6 @@ "@commitlint/config-angular": "^8.3.4", "@types/lodash": "^4.14.119", "@types/node": "^10.12.18", - "@types/react": "^16.9.2", - "@types/react-dom": "^16.9.0", "babel-eslint": "^10.0.1", "babel-loader": "^8.0.4", "benchmark": "^2.1.4", @@ -94,4 +92,4 @@ "webpack": "^4.26.1", "webpack-cli": "^3.1.2" } -} +} \ No newline at end of file diff --git a/packages/g-canvas/src/canvas.ts b/packages/g-canvas/src/canvas.ts index ed618dcd0..f5d21f88e 100644 --- a/packages/g-canvas/src/canvas.ts +++ b/packages/g-canvas/src/canvas.ts @@ -5,7 +5,7 @@ import { getShape } from './util/hit'; import * as Shape from './shape'; import Group from './group'; import { applyAttrsToContext, drawChildren, getMergedRegion, mergeView } from './util/draw'; -import { getPixelRatio, requestAnimationFrame, clearAnimationFrame } from './util/util'; +import { each, getPixelRatio, requestAnimationFrame, clearAnimationFrame } from './util/util'; class Canvas extends AbstractCanvas { getDefaultCfg() { @@ -169,6 +169,7 @@ class Canvas extends AbstractCanvas { // 绘制局部 _drawRegion() { const context = this.get('context'); + const refreshElements = this.get('refreshElements'); const children = this.getChildren() as IElement[]; const region = this._getRefreshRegion(); // 需要注意可能没有 region 的场景 @@ -186,6 +187,13 @@ class Canvas extends AbstractCanvas { drawChildren(context, children, region); context.restore(); } + each(refreshElements, (element) => { + if (element.get('hasChanged')) { + // 在视窗外的 Group 元素会加入到更新队列里,但实际却没有执行 draw() 逻辑,也就没有清除 hasChanged 标记 + // 即已经重绘完、但 hasChanged 标记没有清除的元素,需要统一清除掉。主要是 Group 存在问题,具体原因待排查 + element.set('hasChanged', false); + } + }); this.set('refreshElements', []); } diff --git a/packages/g-canvas/src/group.ts b/packages/g-canvas/src/group.ts index 57257ed92..6e3aaa799 100644 --- a/packages/g-canvas/src/group.ts +++ b/packages/g-canvas/src/group.ts @@ -4,7 +4,6 @@ import { IElement } from './interfaces'; import { Region } from './types'; import ShapeBase from './shape/base'; import * as Shape from './shape'; -import { each, mergeRegion } from './util/util'; import { applyAttrsToContext, drawChildren, refreshElement } from './util/draw'; class Group extends AbstractGroup { diff --git a/packages/g-canvas/src/shape/base.ts b/packages/g-canvas/src/shape/base.ts index 0bb260c76..76aedf391 100644 --- a/packages/g-canvas/src/shape/base.ts +++ b/packages/g-canvas/src/shape/base.ts @@ -90,6 +90,8 @@ class ShapeBase extends AbstractShape { // 是否相交需要考虑 clip 的包围盒 const bbox = clip ? getMergedRegion([this, clip]) : this.getCanvasBBox(); if (!intersectRect(region, bbox)) { + // 图形的包围盒与重绘区域不相交时,也需要清除标记 + this.set('hasChanged', false); return; } } diff --git a/packages/g-canvas/tests/bugs/issue-380-spec.js b/packages/g-canvas/tests/bugs/issue-380-spec.js index 879eb1263..861ead635 100644 --- a/packages/g-canvas/tests/bugs/issue-380-spec.js +++ b/packages/g-canvas/tests/bugs/issue-380-spec.js @@ -28,7 +28,7 @@ describe('#380', () => { clientY, }); - // 要模拟 drag 事件,需要触发 mousemove 事件两次以上。因为第一次 mousemove 事件是用来触发 dragstart 事件的 + // 要模拟 drag 事件,需要触发 mousemove 事件两次及以上。因为第一次 mousemove 事件是用来触发 dragstart 事件的 simulateMouseEvent(el, 'mousemove', { clientX: clientX + 20, clientY, diff --git a/packages/g-canvas/tests/bugs/issue-456-spec.js b/packages/g-canvas/tests/bugs/issue-456-spec.js index 3041fae90..105c4c53e 100644 --- a/packages/g-canvas/tests/bugs/issue-456-spec.js +++ b/packages/g-canvas/tests/bugs/issue-456-spec.js @@ -78,7 +78,7 @@ describe('#456', () => { clientY, }); // 鼠标再次移动,才会触发 drag 事件 - // 要模拟 drag 事件,需要触发 mousemove 事件两次以上。因为第一次 mousemove 事件是用来触发 dragstart 事件的 + // 要模拟 drag 事件,需要触发 mousemove 事件两次及以上。因为第一次 mousemove 事件是用来触发 dragstart 事件的 simulateMouseEvent(el, 'mousemove', { clientX: clientX + 20, clientY, diff --git a/packages/g-canvas/tests/bugs/issue-494-spec.js b/packages/g-canvas/tests/bugs/issue-494-spec.js new file mode 100644 index 000000000..93b43d1f6 --- /dev/null +++ b/packages/g-canvas/tests/bugs/issue-494-spec.js @@ -0,0 +1,120 @@ +const expect = require('chai').expect; +import Canvas from '../../src/canvas'; +import { simulateMouseEvent, getClientPoint } from '../util'; +import { getColor } from '../get-color'; + +const dom = document.createElement('div'); +document.body.appendChild(dom); +dom.id = 'c1'; +dom.style.border = '1px solid black'; +dom.style.display = 'inline-block'; + +describe('#494', () => { + const canvas = new Canvas({ + container: dom, + width: 400, + height: 400, + }); + + const el = canvas.get('el'); + const context = canvas.get('context'); + const pixelRatio = canvas.getPixelRatio(); + + it('render should work correctly when element is clipped by view', (done) => { + const group = canvas.addGroup(); + const rect = group.addShape('rect', { + draggable: true, + attrs: { + fill: 'red', + stroke: 'blue', + lineWidth: 4, + height: 40, + width: 40, + x: 100, + y: 100, + }, + }); + + canvas.on('click', () => { + group.translate(0, 200); + }); + + let origin = {}; + + rect.on('dragstart', (e) => { + origin = { + x: e.clientX, + y: e.clientY, + }; + }); + + rect.on('drag', (e) => { + const clientX = +e.clientX; + const clientY = +e.clientY; + if (isNaN(clientX) || isNaN(clientY)) { + return; + } + + group.translate(clientX - origin.x, clientY - origin.y); + origin = { + x: clientX, + y: clientY, + }; + }); + + /* 先将 rect 完全拖离视窗,并点击画布 */ + // 先移动到 rect 上 + const { clientX, clientY } = getClientPoint(canvas, 100, 100); + simulateMouseEvent(el, 'mousemove', { + clientX, + clientY, + }); + // 按下鼠标 + simulateMouseEvent(el, 'mousedown', { + clientX, + clientY, + }); + // 先移动 10 像素,触发 dragstart 事件 + simulateMouseEvent(el, 'mousemove', { + clientX, + clientY: clientY - 10, + }); + // 要模拟 drag 事件,需要触发 mousemove 事件两次及以上。因为第一次 mousemove 事件是用来触发 dragstart 事件的 + simulateMouseEvent(el, 'mousemove', { + clientX, + clientY: clientY - 20, + }); + // 将 rect 完全拖离视窗,这里通过鼠标离开画布足够距离进行模拟,拖拽距离共 200 = 210 - 10 + simulateMouseEvent(el, 'mousemove', { + clientX, + clientY: clientY - 210, + }); + // 松开鼠标,停止拖拽 + simulateMouseEvent(el, 'mouseup', { + clientX, + clientY: clientY - 200, + }); + let canvasBBox = group.getCanvasBBox(); + // 判断 rect 是否完全拖离画布 + expect(canvasBBox.maxY).eqls(-58); + // 按下鼠标 + simulateMouseEvent(el, 'mousedown', { + clientX, + clientY, + }); + simulateMouseEvent(el, 'mouseup', { + clientX, + clientY, + }); + canvasBBox = group.getCanvasBBox(); + // 判断 rect 是否移动回画布 + expect(canvasBBox.maxY).eqls(142); + setTimeout(() => { + // stroke 判断 + expect(getColor(context, 100 * pixelRatio, 100 * pixelRatio)).eqls('#0000ff'); + // fill 判断 + expect(getColor(context, (100 + 10) * pixelRatio, (100 + 10) * pixelRatio)).eqls('#ff0000'); + done(); + }, 25); + }); +});