iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 3
1
Modern Web

再談 PixiJS,那些先前不一定有提到的部分與地雷系列 第 3

[Re:PixiJS - Day03] 可視物件的深度 - childIndex / zIndex

先前的系列文提過,
可透過 addChild(child) 將可視物件加到容器,並存放在 children 陣列裡

[PixiJS - Day-07] stage、PIXI.Container 與父子結構


Q: 物件的深度是怎麼處理的?

選項1: z-index,隔壁 CSS 就這麼作
選項2: 哪有z-index,都說忘了 CSS,當然是用陣列的順序來調整啊

答案是:都正確

PIXI 裡使用的 zIndex 為小駝峰,與 CSS 屬性的 z-index 不同
此篇接著會以 zIndex 來寫

children 是陣列,先討論操作陣列的 setChildIndex()

實作 setChildIndex() 的 PIXI.Container 類別:

準備 1: 準備一個 createBox() 畫方塊的方法,可指定顏色大小與名稱
function createBox(color, w, h, name){
	const boxContainer = new PIXI.Container();
	boxContainer.name = name;
	const box = new PIXI.Graphics();
	box.beginFill(color);
	box.drawRect(0, 0, 100, 100);
	box.endFill();
	
	const infoStyle = new PIXI.TextStyle({
		fontSize: 12,
		fill: 0xffffff
	});
	const info = new PIXI.Text(name, infoStyle);
	info.x = 5;
	info.y = 5;
	boxContainer.addChild(box, info);

	return boxContainer;
};
準備 2: 各個 Demo 裡會把 app.stage.children 裡的內容印出來
app.stage.children.forEach((child, index)=>{
	console.log(index, child.name, child.zIndex);
});

[ Demo-1 ]

準備兩個方塊並用 addChild(child) 加到場景上,
不特別排序深度時,後產生的會放在上頭

const box1 = createBox(0xff0000, 100, 100, "box1");
const box2 = createBox(0xff9900, 100, 100, "box2");

box1.x = 20;
box1.y = 20;
box2.x = 70;
box2.y = 90;

app.stage.addChild(box1);
app.stage.addChild(box2);

沒有無意外的,box2 蓋在 box1 上頭


[ Demo-2 ]

接著放入一個 box3,放在最下頭
使用 addChildAt(child, 0) 的方式,直接放在最下方
see also:PIXI.Container#addChildAt

const box3 = createBox(0xff0000, 100, 100, "box3");
box3.x = 20;
box3.y = 220;
app.stage.addChildAt(box3, 0);

box3 會根據指定,放在最下方
此時看到的順序由下往上為 box3 < box1 < box2 (配合陣列順序大的寫在右邊)

這時 children 陣列的順序就變有趣了
box3 應該會在陣列的最後一筆,或是第一筆?

ans: 陣列順序與看到的相同,就是 [box3, box1, box2]

(陣列從0 開始,也就是從最下往上排)

PS. Demo 截圖就是答案了,為方便閱讀沒有另外再截一張


[ Demo-3 ]

使用 setChildIndex(child, index) 的方式,直接放在最下方
see also:PIXI.Container#setChildIndex

app.stage.setChildIndex(box1, 2)

順利把 box1 挪到上頭,此時的陣列順序與看到的排列順序皆為 box3 < box2 < box1

關於 setChildIndex(box1, 2):

children 的長度是3,setChildIndex 時,指定的 index 從 0 開始
因此,把 box1 提到最上方的方式
app.stage.setChildIndex(box1, 2); // 總數 3, 減掉從 0 開始的 1

需注意的是,直接指定childIndex時,指定的index不可超出陣列範圍,
也就是不可 小於0 或是 大於陣列長度 -1,否則會跳出錯誤

錯誤訊息為:

addChildI(child) 與 addChildAt(child, index) 都是直接對 children 陣列做操作
在我知道可以用 zIndex 排列前,我都是直接把 children 的順序當作同層級可是物件的 z 順序

註:swapChilden() 也是直接調整 children 陣列的順序,這邊就不多說

實作 zIndex 的 DisplayObject 類別:

可視物件(DisplayObject) 都有 zIndex,同容器的不同可視物件可以有相同的 zIndex

更新測試程式: 印出 app.stage.children 的內容時,把 zindex 也印出來
app.stage.children.forEach((child, index)=>{
	console.log(index, child.name, child.zIndex);
});

[ Demo-4 ]

重新一次測試,放上三個,box:

const box1 = createBox(0xff0000, 100, 100, 'box1');
const box2 = createBox(0xff9900, 100, 100, 'box2');
const box3 = createBox(0xff0000, 100, 100, 'box3');

可以看到每個物件的 zIndex 相同,皆為 0


[ Demo-5 ]

稍早提到 zIndex 可以改變物件順序,
但直接指定 zIndex 不會改變順序:

box2.zIndex = 100;

zIndex 說明:

zIndex
The zIndex of the displayObject. If a container has the sortableChildren property set to true, children will be automatically sorted by zIndex value; a higher value will mean it will be moved towards the end of the array, and thus rendered on top of other displayObjects within the same container.


[ Demo-6 ]

指定完 zIndex後還再執行 sortChildren() 後,就會依照 zIndex 的設定排序:

box2.zIndex = 100;
app.stage.sortChildren();

有趣的事情發生了
除了會依照 zIndex 排序外,children 裡的陣列順序也改變了!


sortableChildren

sortableChildren 為 true 時
自動依據 zIndex 數值大小排列,不需另外 sortChildren();

容器元件的 sortableChildren 預設為 false
this.sortableChildren = settings.SORTABLE_CHILDREN;
可從 global 設定更改


[ Demo-7 ]

更有趣的事情發生了
sortableChildren 設定為 true 且 zIndex 改變時,
並不會立即改變陣列順序,即使隔了 10ms 還是沒更新。
隔了 100ms 後,因為 updateTransform() 執行,深度更新

說明:

sortableChildren
If set to true, the container will sort its children by zIndex value when updateTransform() is called, or manually if sortChildren() is called.

不一定會踩到,但滿有趣的

本篇提到各種方法實作與可視物件的繼承有關,後續的內容會提到


本篇補充:

在執行 array 迴圈裡,調整陣列順序會發生問題:

不小心犯過,加上小補充

可視範圍的深度基本上是陣列
這邊使用 for 迴圈跑陣列來解釋這個問題:

const arr = ["box1", "box2", "box3", "box4"];
for(let i = 0; i<arr.length; i++){
	console.log(i, arr[i]);
	if(i === 0){
		arr.shift(); // 把第一個元素刪除
	}
}

執行迴圈時,陣列長度為 4,會執行 4 次
列出每次執行的情形:

// ["box1", "box2", "box3", "box4"]; // 陣列一開始的樣子

console.log(0, arr[0]); // 第一次,i 為 0; "box1"
arr.shift();  // "box1" 移除; 目前是 ["box2", "box3", "box4"]
console.log(1, arr[1]); // 第一次,i 為 1; "box3"; 此時的 arr[1] 是 "box3"
console.log(2, arr[2]); // 第一次,i 為 2; "box4";
console.log(3, arr[3]); // undefined; i 不是 3

由於在跑第一次的時候,就改變了陣列的順序
在後續的迴圈裡,很容易發生預料外的問題

小結: 不該在迴圈裡調整陣列順序
同理,不該在容器的 children 陣列裡,邊跑迴圈邊更新子物件的深度


上一篇
[Re:PixiJS - Day02] PixiJS 物件的排列、寬高特性
下一篇
[Re:PixiJS - Day04] 元件被遮罩裁切後的寬高
系列文
再談 PixiJS,那些先前不一定有提到的部分與地雷45

尚未有邦友留言

立即登入留言