Designing z-order terminology for a whiteboard app in 2d space

· Medium ·

5 min read Original article ↗

Meet Zaveri

Have you wondered , like how I can correctly order two-dimensional objects which are overlapping each other in an app which uses SVG rendering mechanism?

Let me share you my process on how I tackled z-order i.e. overlapping two-dimensional objects, in a javascript app (craftbase.org - an online open source whiteboard). Craftbase uses two.js as it’s 2d rendering engine . Two.js , is renderer agnostic enabling the same api to render in multiple contexts: webgl, canvas2d, and svg .

Well as they say in web development, when we are faced with an issue where elements are overlapping each other, we know the problem. It’s z-index problem. In SVG, especially, you cannot use z-index property reliably because SVG elements do not follow the CSS box model. They follow painter’s model.

Ironically, also, z-index (or z-order) term is familiar for 3-dimensional space, but here we are using it for managing element’s “Vertically stacked order” in 2-dimensional space. Its same as you would think of that css property z-index . It allows us to place elements in front or behind other elements in the same.

Press enter or click to view image in full size

Just assume z-index is for demonstration purpose, because it doesn’t work for some SVG elements

The problem:

In a digital online whiteboard, if you have multiple elements overlapping same space and area, it seems like the element which should be on top , seems to be behind another element.

If you see the example below, the red bar should’ve been on top of the drawn grid lines just like the green bar is. The difference here is , the elements would show in order as last in, first up. So red bar was there before grid was drawn and the last element to be drawn on whiteboard was green bar. So there was no way we could bring red bar up on z-order space on level with green bar.

Press enter or click to view image in full size

A user , who would be unaware on how to bring it forward, would look for options in edit properties toolbar for that selected shape or either perform right click to see what’s available to them. I’ll come to this point shortly.

Solution

Since we were using two.js as rendering engine, it had already API for it for shapes. The way it works is two.children is the main group for the scene and holds all the group elements in a single array. If we change the individual element’s position in that array, higher they placed , the higher they show up in the elements array, the more visibility they get than those placed in lower indices of the array in comparision.

Press enter or click to view image in full size

For this value to be persisted , I created top level column position (type: integer) for my component table which stores the order value.

The logic for bringing any element back/front, is simple — the order in which they were created , we need to change the order of those elements. For suppose there are 10 elements, but only circle (at position: 7) and rectangle (at position:6) are the ones are overlapping each other, and we want rectangle at position:7 to be in front of circle (position:6), what we need to do is , change the order in two.children since the two’s scene is one big group containing all the elements which we create on board. As well as change the position in table to reflect that and persist so whenver user returns to the board, they see the correct order (computation).

Designing for the audience: The “stack” icon variations for CTA button for each operation

Here you can see while designing, the immediate thought came to mind was the icon with stack of two squares overlapping with each other slightly displaced in x,y values with subtle variations among them. Each one should describe what it’s meant to do in a 2d canvas and user can get instant sense on performing that specific operation.

Press enter or click to view image in full size

Just assume z-index is for demonstration purpose, because it doesn’t work for some SVG elements

Here are four buttons/four operations:

  • For front (at Nth level) — Use icon with page with “N” on top with filled page back.
  • For forward (one level up) — Use icon with page with “1” on top with filled page back
  • For backward (one level back) — Use icon to demonstrate that it pushes back on top of other pages.
  • For back (at last) — Use same as above, but “N” on back, which is not seen at first glance (you’ll have to carefully see it in zoomed manner)

This terminology with these set of icons, I thought would be best to educate users on what reorder means and how it works on each button/operation on a 2d canvas.

Now the real action after implementation. Here is the demo on how red square (which was inserted on the first time, 1st in `two.children[]` array ) , can be brought forward on z-order space on cavnas by triggering “send forward” operation.

Press enter or click to view image in full size

Live demo on how z-order works in craftbase

position sorts ascending, back → front. So:

  • Lower position → further back (rendered first, sits underneath)
  • Higher position → further front (rendered last, sits on top)

You can try it live in this craftbase board (its public shared board).

It acts in a same way z-index property in css works. Higher the value, higher it appears in overlapping elements over those with lower value.

The z-index CSS property doesn’t work for SVG elements, so two.js is the one who decides the order of <g> nodes in document tree.

Misc:

Here’s how the chain connects:

store record .position (custom int)
│ compareByZOrder sorts ascending

order of elements in two.scene.children (JS array)
│ two.update() → SVG renderer syncs DOM to array order

order of <g> nodes inside the SVG (document order)
│ painter's algorithm

what visually stacks on top.

two.update() is a function which update positions and calculations in one pass before rendering. Then render to the canvas.