Skip to content

Commit

Permalink
fix(g-canvas): hasChanged should have priority over refresh with in c…
Browse files Browse the repository at this point in the history
…heckChildrenRefresh (antvis#623)

* fix(g-canvas): render should work correctly when element is clipped by view, close antvis#494 (antvis#550)

* Publish

 - @antv/g-canvas@0.4.13

* fix: switch branch (antvis#574)

* Publish

 - @antv/g-canvas@0.4.14

* fix(g-canvas): hasChanged should have priority over refresh with in checkChildrenRefresh, close antvis#622

Co-authored-by: Yanyan Wang <yanyanwang93@gmail.com>
Co-authored-by: 诸岳 <fuping.dfp@antgroup.com>
  • Loading branch information
3 people authored Sep 15, 2020
1 parent 3063f99 commit a2fd4f4
Show file tree
Hide file tree
Showing 12 changed files with 269 additions and 25 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,4 @@
"webpack": "^4.26.1",
"webpack-cli": "^3.1.2"
}
}
}
2 changes: 1 addition & 1 deletion packages/g-base/src/abstract/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ function removeChild(container: IContainer, element: IElement, destroy: boolean
}

function getComparer(compare: Function) {
return function(left, right) {
return function (left, right) {
const result = compare(left, right);
return result === 0 ? left[INDEX] - right[INDEX] : result;
};
Expand Down
13 changes: 10 additions & 3 deletions packages/g-canvas/src/canvas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { IElement } from './interfaces';
import { getShape } from './util/hit';
import * as Shape from './shape';
import Group from './group';
import { each, getPixelRatio, requestAnimationFrame, clearAnimationFrame } from './util/util';
import { applyAttrsToContext, drawChildren, getMergedRegion, mergeView, checkRefresh, clearChanged } from './util/draw';
import { getPixelRatio, requestAnimationFrame, clearAnimationFrame } from './util/util';

class Canvas extends AbstractCanvas {
getDefaultCfg() {
Expand Down Expand Up @@ -54,7 +54,7 @@ class Canvas extends AbstractCanvas {
getPixelRatio() {
const pixelRatio = this.get('pixelRatio') || getPixelRatio();
// 不足 1 的取 1,超出 1 的取整
return pixelRatio >= 1 ? Math.floor(pixelRatio) : 1;
return pixelRatio >= 1 ? Math.ceil(pixelRatio) : 1;
}

getViewRange() {
Expand Down Expand Up @@ -174,9 +174,9 @@ 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();
const refreshElements = this.get('refreshElements');
// 需要注意可能没有 region 的场景
// 一般发生在设置了 localRefresh ,在没有图形发生变化的情况下,用户调用了 draw
if (region) {
Expand All @@ -201,6 +201,13 @@ class Canvas extends AbstractCanvas {
// 如果不进行清理 hasChanged 的状态会不正确
clearChanged(refreshElements);
}
each(refreshElements, (element) => {
if (element.get('hasChanged')) {
// 在视窗外的 Group 元素会加入到更新队列里,但实际却没有执行 draw() 逻辑,也就没有清除 hasChanged 标记
// 即已经重绘完、但 hasChanged 标记没有清除的元素,需要统一清除掉。主要是 Group 存在问题,具体原因待排查
element.set('hasChanged', false);
}
});
this.set('refreshElements', []);
}

Expand Down
1 change: 1 addition & 0 deletions packages/g-canvas/src/shape/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ class ShapeBase extends AbstractShape {
// 是否相交需要考虑 clip 的包围盒
const bbox = this.getCanvasBBox();
if (!intersectRect(region, bbox)) {
// 图形的包围盒与重绘区域不相交时,也需要清除标记
this.set('hasChanged', false);
// 存在多种情形需要更新 cacheCanvasBBox 和 isInview 的判定
// 1. 之前图形在视窗内,但是现在不再视窗内
Expand Down
13 changes: 7 additions & 6 deletions packages/g-canvas/src/util/draw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,17 +74,18 @@ export function checkChildrenRefresh(children: IElement[], region: Region) {
for (let i = 0; i < children.length; i++) {
const child = children[i] as IElement;
if (child.cfg.visible) {
// 如果当前图形/分组 refresh = true,说明其子节点存在 changed
if (child.cfg.refresh) {
if (child.isGroup()) {
checkChildrenRefresh(child.cfg.children, region);
}
} else if (child.cfg.hasChanged) {
// 先判断 hasChanged,因为它的优先级判断应该高于 refresh
if (child.cfg.hasChanged) {
// 如果节点发生了 change,则需要级联设置子元素的 refresh
child.cfg.refresh = true;
if (child.isGroup()) {
setChildrenRefresh(child.cfg.children, region);
}
} else if (child.cfg.refresh) {
// 如果当前图形/分组 refresh = true,说明其子节点存在 changed
if (child.isGroup()) {
checkChildrenRefresh(child.cfg.children, region);
}
} else {
// 这个分支说明此次局部刷新,所有的节点和父元素没有发生变化,仅需要检查包围盒(缓存)是否相交即可
const refresh = checkElementRefresh(child, region);
Expand Down
2 changes: 1 addition & 1 deletion packages/g-canvas/tests/bugs/issue-380-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ describe('#380', () => {
clientY,
});

// 要模拟 drag 事件,需要触发 mousemove 事件两次以上。因为第一次 mousemove 事件是用来触发 dragstart 事件的
// 要模拟 drag 事件,需要触发 mousemove 事件两次及以上。因为第一次 mousemove 事件是用来触发 dragstart 事件的
simulateMouseEvent(el, 'mousemove', {
clientX: clientX + 20,
clientY,
Expand Down
2 changes: 1 addition & 1 deletion packages/g-canvas/tests/bugs/issue-456-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ describe('#456', () => {
clientY,
});
// 鼠标再次移动,才会触发 drag 事件
// 要模拟 drag 事件,需要触发 mousemove 事件两次以上。因为第一次 mousemove 事件是用来触发 dragstart 事件的
// 要模拟 drag 事件,需要触发 mousemove 事件两次及以上。因为第一次 mousemove 事件是用来触发 dragstart 事件的
simulateMouseEvent(el, 'mousemove', {
clientX: clientX + 20,
clientY,
Expand Down
120 changes: 120 additions & 0 deletions packages/g-canvas/tests/bugs/issue-494-spec.js
Original file line number Diff line number Diff line change
@@ -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);
});
});
115 changes: 115 additions & 0 deletions packages/g-canvas/tests/bugs/issue-622-spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
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('#622', () => {
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('hasChanged should have priority over refresh with in checkChildrenRefresh function', (done) => {
const group = canvas.addGroup();

const circle = group.addShape('circle', {
draggable: true,
attrs: {
x: 100,
y: 100,
r: 50,
fill: '#f00',
},
});

const rect = group.addShape('rect', {
attrs: {
x: 100,
y: 100,
fill: '#0f0',
width: 100,
height: 50,
},
});

rect.animate(
{
width: 50,
},
{
duration: 1000,
repeat: true,
}
);

let previousX = 0;
let previousY = 0;

circle.on('dragstart', (e) => {
previousX = e.clientX;
previousY = e.clientY;
});

circle.on('drag', (e) => {
const dx = e.clientX - previousX;
const dy = e.clientY - previousY;
group.translate(dx, dy);
previousX = e.clientX;
previousY = e.clientY;
});

/* 先将 group 完全拖离视窗,并点击画布 */
// 先移动到 circle 上
const { clientX, clientY } = getClientPoint(canvas, 90, 90);
// 按下鼠标
simulateMouseEvent(el, 'mousedown', {
clientX,
clientY,
});
// 先移动 10 像素,触发 dragstart 事件
simulateMouseEvent(el, 'mousemove', {
clientX,
clientY: clientY - 10,
});
// // 往回移动 10px 回到起始地点
// // 要模拟 drag 事件,需要触发 mousemove 事件两次及以上。因为第一次 mousemove 事件是用来触发 dragstart 事件的
simulateMouseEvent(el, 'mousemove', {
clientX,
clientY,
});
// 将 group 完全拖离视窗,这里通过鼠标离开画布足够距离(点击处上方 200 的位置)进行模拟
simulateMouseEvent(el, 'mousemove', {
clientX,
clientY: clientY - 200,
});
// 将 group 拖拽回起始位置
simulateMouseEvent(el, 'mousemove', {
clientX,
clientY: clientY - 10,
});
// 松开鼠标,停止拖拽
simulateMouseEvent(el, 'mouseup', {
clientX,
clientY,
});
const canvasBBox = group.getCanvasBBox();
// 判断 group 是否拖拽回起始位置
expect(canvasBBox.maxY).eqls(150);
setTimeout(() => {
// 判断 circle 能否正常渲染
expect(getColor(context, 90 * pixelRatio, 90 * pixelRatio)).eqls('#ff0000');
done();
}, 25);
});
});
4 changes: 2 additions & 2 deletions packages/g-canvas/tests/unit/canvas-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,9 @@ describe('canvas test', () => {
expect(canvas.get('pixelRatio')).eql(0.8);
expect(canvas.getPixelRatio()).eql(1);
canvas.set('pixelRatio', 1.8);
expect(canvas.getPixelRatio()).eql(1);
canvas.set('pixelRatio', 2.8);
expect(canvas.getPixelRatio()).eql(2);
canvas.set('pixelRatio', 2.8);
expect(canvas.getPixelRatio()).eql(3);
});

it('destroy', () => {
Expand Down
14 changes: 7 additions & 7 deletions packages/g-svg/src/shape/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,16 @@ class Dom extends ShapeBase {
if (typeof attrs['html'] === 'function') {
const element = attrs['html'].call(this, attrs);
if (element instanceof Element || element instanceof HTMLDocument) {
const children = el.childNodes;
for(let i = children.length - 1; i >= 0; i--) {
el.removeChild(children[i]);
}
el.appendChild(element); // append to el if it's an element
const children = el.childNodes;
for (let i = children.length - 1; i >= 0; i--) {
el.removeChild(children[i]);
}
el.appendChild(element); // append to el if it's an element
} else {
el.innerHTML = element; // set innerHTML
el.innerHTML = element; // set innerHTML
}
} else {
el.innerHTML = attrs['html']; // set innerHTML
el.innerHTML = attrs['html']; // set innerHTML
}
}
}
Expand Down
Loading

0 comments on commit a2fd4f4

Please sign in to comment.