Konva does not have css-type selectors, so what does it offer instead?

Used to using CSS selectors to work on the HTML DOM and wondering if there is anything similar in Konva? Here’s your answer!

TLDR: Here’s example code showcasing how to do selectors in Konva.

Lets just clear up our understanding of what Konva is. It’s a wrapper for the HTML5 canvas.

The HTML5 canvas does not have elements in the same way that the DOM does. Let’s consider an HTML5 frame element for a moment. A frame, like a canvas, is a DOM element in its own right and it has it’s own DOM where all those CSS selectors can be used. But a canvas element does not have its own HTML DOM.

In its simplest form, we can think of a canvas as containing not ‘elements‘ but simple drawn ‘shapes‘ that have no individual existence, no selectors, no properties, nothing. Konva and other canvas libraries provide an object model that allow those drawn shapes to take on object-like properties that we can manipulate. This is conceptually a type of DOM but is NOT THE SAME AS the HTML DOM.

If you think about it objectively, the canvas is about hosting high-quality graphics and animation. If you need to do HTML element stuff use a frame – if you want drawing stuff use a canvas.

Which is all cool, except that learning to code JavaScript and the DOM go hand-in-hand and its a well trodden route taken by the majority. And if you are coding JS & DOM then you will be used to using selectors. There are a vast number of libraries for this, you can even do it in Vanilla JS, but they all have the same principles. I know you know this, but I’m going to give examples using pure CSS, JQuery, and plain JavaScript to establish a baseline and then show you how to achieve the same in Konva. If you are cool with selectors jump ahead to ‘Selecting the Konva way

Select by tag name

In HTML, each element has a specific type, known formally as the ‘tagname’. Search by tagname is expected to return zero or many hits. Assuming the DOM has some paragraph elements, we can format a select/search as:

// CSS selector
#p {
  border: 1px solid red;
}

// jquery
let paras= $('p');

// pure JS
let paras = document.getElementsByTagName('p');

View the Konva way to search by tag name.

Select by ID

Each element may have an ID property, which should be unique. Selecting by ID is expected to return zero or one hit. Assuming there is an element in the DOM having ID property equal to ‘shape_103’, we can format a search for it as follows:

// CSS selector
#shape_103 {
  border: 1px solid red;
}

// jquery
let ele = $('#shape_103');

// pure JS
let ele = document.getElementById('shape_103');

Each of these would target or return the element with ID equal to ‘shape_103’.

View the Konva way to search by id.

Select by class

Each element can have one or more class names assigned via the class property. Selecting by class selector is expected to return zero or many hits. Assuming there is at least one element with its class property including the word ‘Circle’, we could select them via:

// CSS selector
.Circle{
  border: 1px solid blue;
}

// jquery
let circles= $('.Circle');

// pure JS
let circles = document.getElementsByClassName('Circle');

The CSS selector would target all elements with class including ‘Circle’, whilst the jquery and plain JS would return a list of matching elements.

View the Konva way to search by class

Select by data attribute with value

In JS & DOM coding, storing data values within DOM elements is a standard tactic. Selecting by data property name and value is expected to return zero or many elements. The way to retrieve or match these elements is:

// html element definition
<div  data-color='red'>Ipsum lorem...</div>

// CSS selector
[data-color="red"],
  border: 1px solid gold;
}

// jquery
let elements = $('[data-color="red"]');

// pure JS
let elements = document.querySelectorAll('[data-color="red"]');

View the Konva way to search by data attributes.

OK – that was a rundown of the everyday selectors and how to use them in a few common ways. Now let’s see how you can achieve the same in Konva.

Selecting the Konva way

So, I said already that Konva does not have the CSS selectors mechanism we have every day for breakfast. But we can search in Konva using the Konva find() method. This takes a selector-type parameter which varies as described in the cases below.

Note that find returns a list of hits. When you want only the first hit use the findOne() method instead.

Konva equivalent to tag name search

Kinds of ‘thing’ must have a name, and being derived from an object-orientated perspective, Konva follows this requirement with the Shape ‘className’ property. Every object in the Konva world has a className, including the stage, layers, groups, and drawn shapes.

For example, the className for a Konva Rect is ‘Rect’, a Circle is ‘Circle’, but it becomes a bit more abstracted when it comes to triangles, pentagons, etc which are className ‘RegularPolygon’.

The find selector for a className search is simply the name of the shape. For example, the internal className for a Konva.Rect is ‘Rect’, meaning a search for all rectangles would be:

// Make 10 blue and 10 red rectangles
const colors = ['blue', 'red'];
for (let j = 0; j < colors.length; j++)
{
	for (let i = 0; i < 10; i++){
		const r = new Konva.Rect({
		  x: randomX(),
		  y: randomY(),
		  width: 100,
		  height: 50,
		  fill: colors[j],
		  name: 'Rect ' + colors[j]
		})
		layer.add(r);
    console.log(r.toJSON())
	}
}

// do some other stuff....and then later....

// search for all rect shapes 
const shapeList = stage.find('Rect');

// Draw a border around the found shapes.
for (let i = 0; i < shapeList.length; i++){
	shapeList[i].setAttrs({
		strokeWidth: 2,
		stroke: "black",
	});
	shapeList[i].moveToTop();  // move on top of other objects
}

Konva equivalent to ID search

Just like in the case of HTML elements, Konva Shapes can have an optional ID property. We search for this as follows

// Make a rectangle 
const r = new Konva.Rect({
  x: 10,
  y: 20,
  width: 100,
  height: 50,
  fill: 'red',
  id: 'shape_103'
})
layer.add(r);

// do some other stuff....and then later....

// search for it by id
const shape= stage.findOne('#shape_103');

// If we found the shape then draw a border around it.
if (shape){
	shape.setAttrs({
		strokeWidth: 2,
		stroke: "black",
	})
	shape.moveToTop();  // move on top of other objects
}

Konva equivalent to HTML class search

Konva reserves the class attribute for its shape type assignment. The closest usable equivalent to the HTML class concept in Konva is ‘name‘ which follows the same format rules as the HTML class property, meaning each name is a whole word, multiple names are separated by spaces, multiple shapes can have the same name. Example ‘Rect red’.

// Make 10 blue and 10 red rectangles
const colors = ['blue', 'red'];
for (let j = 0; j < colors.length; j++)
{
	for (let i = 0; i < 10; i++){
		const r = new Konva.Rect({
		  x: randomX(),
		  y: randomY(),
		  width: 100,
		  height: 50,
		  fill: colors[j],
		  name: 'Rect ' + colors[j]
		})
		layer.add(r);
    console.log(r.toJSON())
	}
}

// do some other stuff....and then later....

// search for all red shapes !! notice the prefix dot for name searches !!
const shapeList = stage.find('.red'); 

// Draw a border around the found shapes.
for (let i = 0; i < shapeList.length; i++){
	shapeList[i].setAttrs({
		strokeWidth: 2,
		stroke: "black",
	});
	shapeList[i].moveToTop();  // move on top of other objects
}

Konva equivalent to data searches

There is no direct match to the HTML DOM concept of data. In fact Konva will let you add attributes to a shape just by setting an attribute via the setAttr() or setAttrs() methods. Be careful not to use a Konva attribute name by mistake! I tend to use a camel-cased format leading with ‘data’, so for example ‘dataSpeed’ to avoid this trap.

So if there is no direct equivalent to data searches then how do we search? Fortunately find() provides a very powerful variation that allows us to provide a comparison function. This is the same approach as, for example, the Array.filter() process, where the passed-in function can be as simple or complex as needed, and must return true for a hit and false for a miss.

In the example below we add the attribute dataColor to each shape as it is created – you can add/change these attributes using shape.setAttr() later. Then in the find function we return true if the dataColor attribute matches ‘blue’.

// Make 10 blue and 10 red rectangles
const colors = ['blue', 'red'];

for (let j = 0; j < colors.length; j++)
{
  for (let i = 0; i < 10; i++){
    const r = new Konva.Rect({
      x: randomX(),
      y: randomY(),
      width: 100,
      height: 50,
      fill: colors[j],
      dataColor: colors[j]
    })
    layer.add(r);
    console.log(r.toJSON())
  }
}

// do some other stuff....and then later....

// search for all shapes having attr dataColor = 'blue' !! notice we use a function for this !!
const shapeList = stage.find(node => {
  switch (node.getClassName()){
    case "Layer":
    case "Group":
      return false;
      break;
    default: // fires for shapes that are not layer or Group
      return(node.getAttr('dataColor') === 'blue'); // return true to indicate a hit, false otherwise. 
  }
})

// Draw a border around the found shapes.
for (let i = 0; i < shapeList.length; i++){
  shapeList[i].setAttrs({
    strokeWidth: 2,
    stroke: "black",
  });
  shapeList[i].moveToTop();  // move on top of other objects
}

The code in the comparison function can be as complex as needed so that you can code and ‘or’ & ‘and’ tests as you need. And you can access any Konva shape standard attribute via getAttr() meaning that you can check, for example the fill color, the stroke width, the class name, etc.

Finally a point to watch out for if you use the find from the stage – you will find all nodes on the stage including layers, groups, transformers, and shapes. It’s the shapes you are generally looking out for so use a switch statement to filter out the unwanted layer, group and transformer hits.

Summary

We’ve seen that Konva has viable selectors though they differ slightly from those we might have grown up with in the JS & DOM world. We’ve seen how to do simple searches, and how we can run more complex searches using the find & filter function combination.

Thanks for reading.

VW Dec 2021

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: