mirror of
https://github.com/konvajs/konva.git
synced 2025-06-28 06:31:15 +08:00
z-order support for konva
This commit is contained in:
parent
6e5aff393f
commit
c0efb7d1a1
141
src/AbsoluteRenderOrderGroup.ts
Normal file
141
src/AbsoluteRenderOrderGroup.ts
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
import { Util } from './Util';
|
||||||
|
import { Container, ContainerConfig } from './Container';
|
||||||
|
import { _registerNode } from './Global';
|
||||||
|
import { Node } from './Node';
|
||||||
|
import { Shape } from './Shape';
|
||||||
|
import { Group, GroupConfig } from './Group';
|
||||||
|
import { HitCanvas, SceneCanvas } from './Canvas';
|
||||||
|
|
||||||
|
export interface AbsoluteRenderOrderGroupConfig extends GroupConfig { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AbsoluteRenderOrderGroup constructor. AbsoluteRenderOrderGroup is a special kind of Group that renders all of its
|
||||||
|
* children and subchildren recursively, in the order of the z-order parameter.
|
||||||
|
*
|
||||||
|
* In order to maintain masking behavior, cached groups are respected and treated as a single object at the group's
|
||||||
|
* designated z-order.
|
||||||
|
* @constructor
|
||||||
|
* @memberof Konva
|
||||||
|
* @augments Konva.Container
|
||||||
|
* @param {Object} config
|
||||||
|
* @@nodeParams
|
||||||
|
* @@containerParams
|
||||||
|
* @example
|
||||||
|
* var group = new Konva.Group();
|
||||||
|
*/
|
||||||
|
export class AbsoluteRenderOrderGroup extends Group {
|
||||||
|
_validateAdd(child: Node) {
|
||||||
|
var type = child.getType();
|
||||||
|
if (type !== 'Group' && type !== 'Shape') {
|
||||||
|
Util.throw('You may only add groups and shapes to groups.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_drawChildren(drawMethod, canvas, top) {
|
||||||
|
var context = canvas && canvas.getContext(),
|
||||||
|
clipWidth = this.clipWidth(),
|
||||||
|
clipHeight = this.clipHeight(),
|
||||||
|
clipFunc = this.clipFunc(),
|
||||||
|
hasClip = (clipWidth && clipHeight) || clipFunc;
|
||||||
|
|
||||||
|
const selfCache = top === this;
|
||||||
|
|
||||||
|
if (hasClip) {
|
||||||
|
context.save();
|
||||||
|
var transform = this.getAbsoluteTransform(top);
|
||||||
|
var m = transform.getMatrix();
|
||||||
|
context.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
|
||||||
|
context.beginPath();
|
||||||
|
if (clipFunc) {
|
||||||
|
clipFunc.call(this, context, this);
|
||||||
|
} else {
|
||||||
|
var clipX = this.clipX();
|
||||||
|
var clipY = this.clipY();
|
||||||
|
context.rect(clipX, clipY, clipWidth, clipHeight);
|
||||||
|
}
|
||||||
|
context.clip();
|
||||||
|
m = transform.copy().invert().getMatrix();
|
||||||
|
context.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var hasComposition =
|
||||||
|
!selfCache &&
|
||||||
|
this.globalCompositeOperation() !== 'source-over' &&
|
||||||
|
drawMethod === 'drawScene';
|
||||||
|
|
||||||
|
if (hasComposition) {
|
||||||
|
context.save();
|
||||||
|
context._applyGlobalCompositeOperation(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// AbsoluteRenderOrderGroup differs from the standard container by ordering its children itself, instead of
|
||||||
|
// letting children call each.
|
||||||
|
|
||||||
|
let unorderedChildren = new Map<number, Node[]>();
|
||||||
|
|
||||||
|
// Add all children recursively to orderedChildren
|
||||||
|
this.addChildrenRecursivelyToMap(this, unorderedChildren);
|
||||||
|
|
||||||
|
// Sort children by zOrder
|
||||||
|
// ( https://stackoverflow.com/questions/31158902/is-it-possible-to-sort-a-es6-map-object )
|
||||||
|
let orderedChildren = new Map<number, Node[]>([...unorderedChildren].sort(
|
||||||
|
(a, b) =>
|
||||||
|
{
|
||||||
|
if (a[0] > b[0])
|
||||||
|
return 1;
|
||||||
|
else if (a[0] == b[0])
|
||||||
|
return 0;
|
||||||
|
else
|
||||||
|
return -1;
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Draw children in zOrder
|
||||||
|
for (const [zOrder, nodeArray] of orderedChildren)
|
||||||
|
{
|
||||||
|
//console.log("Drawing " + zOrder);
|
||||||
|
for (const node of nodeArray)
|
||||||
|
{
|
||||||
|
//console.log(node)
|
||||||
|
node[drawMethod](canvas, top);
|
||||||
|
//console.log(node.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (hasComposition) {
|
||||||
|
context.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasClip) {
|
||||||
|
context.restore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private addChildrenRecursivelyToMap(node:Node, orderedChildren:Map<number, Array<Node>>):void
|
||||||
|
{
|
||||||
|
let rootNode:AbsoluteRenderOrderGroup = this;
|
||||||
|
|
||||||
|
if (node == rootNode || // the AbsoluteRenderOrderGroup itself will always render using z-order logic, even if cached
|
||||||
|
(node instanceof Group && !node.isCached())) { // However, cached subgroups are considered to be just a regular object (this protects masking)
|
||||||
|
(node as Group).children?.forEach(function (child) {
|
||||||
|
rootNode.addChildrenRecursivelyToMap(child, orderedChildren);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Is a leaf / don't descend farther -- this can be added to children map
|
||||||
|
let zOrder:number = node.zOrder();
|
||||||
|
if (!orderedChildren.has(zOrder))
|
||||||
|
{
|
||||||
|
orderedChildren.set(zOrder, new Array<Node>());
|
||||||
|
}
|
||||||
|
|
||||||
|
orderedChildren.get(zOrder).push(node); // I'd much prefer the [] syntax for clarity, but seems TS/JS doesn't seem to support it, ugh.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//AbsoluteRenderOrderGroup.prototype.nodeType = 'AbsoluteRenderOrderGroup';
|
||||||
|
// Node type appears to be either Shape or Group, so it doesn't seem like this should be set if we mirror how Shapes work?
|
||||||
|
|
||||||
|
AbsoluteRenderOrderGroup.prototype.className = 'AbsoluteRenderOrderGroup';
|
||||||
|
_registerNode(AbsoluteRenderOrderGroup);
|
21
src/Node.ts
21
src/Node.ts
@ -72,6 +72,7 @@ export interface NodeConfig {
|
|||||||
preventDefault?: boolean;
|
preventDefault?: boolean;
|
||||||
globalCompositeOperation?: globalCompositeOperationType;
|
globalCompositeOperation?: globalCompositeOperationType;
|
||||||
filters?: Array<Filter>;
|
filters?: Array<Filter>;
|
||||||
|
zOrder?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// CONSTANTS
|
// CONSTANTS
|
||||||
@ -2618,6 +2619,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
|
|||||||
|
|
||||||
rotation: GetSet<number, this>;
|
rotation: GetSet<number, this>;
|
||||||
zIndex: GetSet<number, this>;
|
zIndex: GetSet<number, this>;
|
||||||
|
zOrder: GetSet<number, this>;
|
||||||
|
|
||||||
scale: GetSet<Vector2d | undefined, this>;
|
scale: GetSet<Vector2d | undefined, this>;
|
||||||
scaleX: GetSet<number, this>;
|
scaleX: GetSet<number, this>;
|
||||||
@ -3278,6 +3280,25 @@ addGetterSetter(Node, 'dragBoundFunc');
|
|||||||
*/
|
*/
|
||||||
addGetterSetter(Node, 'draggable', false, getBooleanValidator());
|
addGetterSetter(Node, 'draggable', false, getBooleanValidator());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get/set z-order position. Note that z-order is not the same as z-index. Z-order is used with the
|
||||||
|
* AbsoluteRenderOrderContainer to recursively render child objects in an absolute z-order. This field will otherwise
|
||||||
|
* be ignored. Alternatively, you can use the z-index features to move an object within a particular group, relative to
|
||||||
|
* the other nodes in the group.
|
||||||
|
* @name Konva.Node#zOrder
|
||||||
|
* @method
|
||||||
|
* @param {Number} zOrder
|
||||||
|
* @returns {Object}
|
||||||
|
* @example
|
||||||
|
* // get z-order
|
||||||
|
* var z = node.zOrder();
|
||||||
|
*
|
||||||
|
* // set z-order
|
||||||
|
* node.zOrder(5);
|
||||||
|
*/
|
||||||
|
|
||||||
|
addGetterSetter(Node, 'zOrder', 0, getNumberValidator());
|
||||||
|
|
||||||
Factory.backCompat(Node, {
|
Factory.backCompat(Node, {
|
||||||
rotateDeg: 'rotate',
|
rotateDeg: 'rotate',
|
||||||
setRotationDeg: 'setRotation',
|
setRotationDeg: 'setRotation',
|
||||||
|
@ -11,6 +11,7 @@ import { Layer } from './Layer';
|
|||||||
import { FastLayer } from './FastLayer';
|
import { FastLayer } from './FastLayer';
|
||||||
|
|
||||||
import { Group } from './Group';
|
import { Group } from './Group';
|
||||||
|
import { AbsoluteRenderOrderGroup } from './AbsoluteRenderOrderGroup';
|
||||||
|
|
||||||
import { DD } from './DragAndDrop';
|
import { DD } from './DragAndDrop';
|
||||||
|
|
||||||
@ -32,6 +33,7 @@ export const Konva = Util._assign(Global, {
|
|||||||
Layer,
|
Layer,
|
||||||
FastLayer,
|
FastLayer,
|
||||||
Group,
|
Group,
|
||||||
|
AbsoluteRenderOrderGroup,
|
||||||
DD,
|
DD,
|
||||||
Shape,
|
Shape,
|
||||||
shapes,
|
shapes,
|
||||||
|
4
src/index-types.d.ts
vendored
4
src/index-types.d.ts
vendored
@ -85,6 +85,10 @@ declare namespace Konva {
|
|||||||
export type Group = import('./Group').Group;
|
export type Group = import('./Group').Group;
|
||||||
export type GroupConfig = import('./Group').GroupConfig;
|
export type GroupConfig = import('./Group').GroupConfig;
|
||||||
|
|
||||||
|
export const AbsoluteRenderOrderGroup: typeof import('./AbsoluteRenderOrderGroup').AbsoluteRenderOrderGroup;
|
||||||
|
export type AbsoluteRenderOrderGroup = import('./AbsoluteRenderOrderGroup').AbsoluteRenderOrderGroup;
|
||||||
|
export type AbsoluteRenderOrderGroupConfig = import('./AbsoluteRenderOrderGroup').AbsoluteRenderOrderGroupConfig;
|
||||||
|
|
||||||
export const DD: typeof import('./DragAndDrop').DD;
|
export const DD: typeof import('./DragAndDrop').DD;
|
||||||
|
|
||||||
export const Shape: typeof import('./Shape').Shape;
|
export const Shape: typeof import('./Shape').Shape;
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
import './unit/DragAndDrop-test.ts';
|
import './unit/DragAndDrop-test.ts';
|
||||||
import './unit/Global-test.ts';
|
import './unit/Global-test.ts';
|
||||||
import './unit/Group-test.ts';
|
import './unit/Group-test.ts';
|
||||||
|
import './unit/AbsoluteRenderOrderGroup-test.ts';
|
||||||
import './unit/Layer-test.ts';
|
import './unit/Layer-test.ts';
|
||||||
import './unit/Util-test.ts';
|
import './unit/Util-test.ts';
|
||||||
import './unit/Stage-test.ts';
|
import './unit/Stage-test.ts';
|
||||||
|
141
test/unit/AbsoluteRenderOrderGroup-test.ts
Normal file
141
test/unit/AbsoluteRenderOrderGroup-test.ts
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
import { assert } from 'chai';
|
||||||
|
import { addStage, isNode, Konva } from './test-utils';
|
||||||
|
|
||||||
|
describe('AbsoluteRenderOrderGroup', function () {
|
||||||
|
// ======================================================
|
||||||
|
it('check render order -- simple, no subgroups', function () {
|
||||||
|
var stage = addStage();
|
||||||
|
|
||||||
|
const layer = new Konva.Layer();
|
||||||
|
stage.add(layer);
|
||||||
|
|
||||||
|
// This will test that AbsoluteRenderOrderGroup renders based on z-order, not z-index
|
||||||
|
const absoluteRenderOrderGroupTest = new Konva.AbsoluteRenderOrderGroup({
|
||||||
|
x: 0,
|
||||||
|
y: 0
|
||||||
|
});
|
||||||
|
layer.add(absoluteRenderOrderGroupTest);
|
||||||
|
|
||||||
|
const redRect = new Konva.Rect({
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
fill: 'red',
|
||||||
|
zOrder: 10 // on top
|
||||||
|
});
|
||||||
|
absoluteRenderOrderGroupTest.add(redRect);
|
||||||
|
|
||||||
|
const blueRect = new Konva.Rect({
|
||||||
|
x: 50,
|
||||||
|
y: 50,
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
fill: 'blue',
|
||||||
|
zOrder: 0 // on bottom
|
||||||
|
});
|
||||||
|
absoluteRenderOrderGroupTest.add(blueRect);
|
||||||
|
|
||||||
|
// Set z-order to be deliberately different from z-index
|
||||||
|
redRect.moveToBottom();
|
||||||
|
|
||||||
|
layer.draw();
|
||||||
|
|
||||||
|
// Check pixel color -- should be Red if AbsoluteRenderOrderGroup is respecting the ordering
|
||||||
|
// (More info: https://stackoverflow.com/questions/667045/get-a-pixel-from-html-canvas )
|
||||||
|
let context = layer.canvas.getContext();
|
||||||
|
let imageData = context.getImageData(55, 55, 1, 1); // this is an intersecting pixel location between red & blue
|
||||||
|
let red = imageData.data[0];
|
||||||
|
|
||||||
|
assert.equal(red, 255, "Did not find red pixel, ordering is possibly incorrect. Red amount found was: " + red);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Test AbsoluteRenderOrderGroup correctly interleaves the ordering of subgroups', function () {
|
||||||
|
var stage = addStage();
|
||||||
|
|
||||||
|
const layer = new Konva.Layer();
|
||||||
|
stage.add(layer);
|
||||||
|
|
||||||
|
const absoluteRenderOrderGroupTest = new Konva.AbsoluteRenderOrderGroup({
|
||||||
|
x: 0,
|
||||||
|
y: 0
|
||||||
|
});
|
||||||
|
layer.add(absoluteRenderOrderGroupTest);
|
||||||
|
|
||||||
|
const group1 = new Konva.Group({
|
||||||
|
x: 0,
|
||||||
|
y: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
const group2 = new Konva.Group({
|
||||||
|
x: 25,
|
||||||
|
y: 25,
|
||||||
|
});
|
||||||
|
|
||||||
|
absoluteRenderOrderGroupTest.add(group1);
|
||||||
|
absoluteRenderOrderGroupTest.add(group2);
|
||||||
|
|
||||||
|
// Add shapes that interleave between the groups
|
||||||
|
// It should render as brightest red -> middle reds -> black
|
||||||
|
|
||||||
|
const rect1 = new Konva.Rect({
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
fill: '#FF0000',
|
||||||
|
zOrder: 10 // on top
|
||||||
|
});
|
||||||
|
group1.add(rect1);
|
||||||
|
|
||||||
|
const rect2 = new Konva.Rect({
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
fill: '#AA0000',
|
||||||
|
zOrder: 7
|
||||||
|
});
|
||||||
|
group2.add(rect2);
|
||||||
|
|
||||||
|
const rect3 = new Konva.Rect({
|
||||||
|
x: 50,
|
||||||
|
y: 50,
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
fill: '#770000',
|
||||||
|
zOrder: 5
|
||||||
|
});
|
||||||
|
group1.add(rect3);
|
||||||
|
|
||||||
|
const rect4 = new Konva.Rect({
|
||||||
|
x: 50,
|
||||||
|
y: 50,
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
fill: 'black',
|
||||||
|
zOrder: 0 // on bottom
|
||||||
|
});
|
||||||
|
group2.add(rect4);
|
||||||
|
|
||||||
|
layer.draw();
|
||||||
|
|
||||||
|
// Check pixel color -- should be Red if AbsoluteRenderOrderGroup is respecting the ordering
|
||||||
|
// (More info: https://stackoverflow.com/questions/667045/get-a-pixel-from-html-canvas )
|
||||||
|
let context = layer.canvas.getContext();
|
||||||
|
|
||||||
|
let widthCapture = 200;
|
||||||
|
let heightCapture = 200;
|
||||||
|
let imageData = context.getImageData(0, 0, widthCapture, heightCapture);
|
||||||
|
|
||||||
|
let red1 = imageData.data[(80+widthCapture*80)*4];
|
||||||
|
let red2 = imageData.data[(115+widthCapture*115)*4];
|
||||||
|
let red3 = imageData.data[(140+widthCapture*140)*4];
|
||||||
|
let black = imageData.data[(160+widthCapture*160)*4];
|
||||||
|
|
||||||
|
assert.equal(red1, 255, "Did not find correct amount of red in bright red pixel for test 1, ordering is possibly incorrect. Red amount found was: " + red1);
|
||||||
|
assert.equal(red2, 170, "Did not find correct amount of red in medium bright red pixel for test 1, ordering is possibly incorrect. Red amount found was: " + red2);
|
||||||
|
assert.equal(red3, 119, "Did not find correct amount of red in medium dark red pixel for test 1, ordering is possibly incorrect. Red amount found was: " + red3);
|
||||||
|
assert.equal(black, 0, "Did not find correct amount of red in black pixel for test 1, ordering is possibly incorrect. Red amount found was: " + black);
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user