How to create a Pan`n`Pinch component for a Figma plugin. Step-by-step Recipe.

Hallo, Leute!

Today I want to shortly describe a component that I wrote for one of my plugins — WarpIt. And I want to share my experience with you.

There were some pitfalls during the coding process but not too much. And what I like here, that at the end the component code looks very clean and understandable.

Just a quick disclaimer — I write all my plugins with React and here as well I will describe a react component. But it doesn’t mean that you can’t take the code and rewrite it a little for your framework or even with the vanila JS. The component is fully isolated and doesn't have complex hooks, only basic event triggers, and targets.

Research

Before I started to write my own plugin, I was trying to find something that would suit my needs.

  • Pan gesture support(because I work on mac and use the touchpad a lot)
  • Hold Spacebar + left mouse click and move the canvas (in case if a user uses a mouse)
  • zoom in/out by pressing +/- or by pinch gesture, and zoom in/ by ctlr + mouse wheel

So, basically, I wanted to repeat the same behavior that Figma has.

I found about three libraries. But all of them contain a lot of extra settings and code that I don’t need. It also was very hard to customize them and something in addition, because I wanted pretty specific behavior and all of these libraries were more for maps or to zoom pictures, but not to handle canvas navigation.

The component layout

The component itself is pretty simple it consists of two parts

We will create the component without any additional libraries and we need to import only React here.

then we will need to specify custom prop types. I’ll it means that we will have some props that could be adjustable. I’ll add to custom props:

  • panSpeedRatio as a number type — it's an index of the pan speed. If we want to make movement faster, just increase this prop. By default, it is 1.4.
  • test as a booleaan type — true or false. This property will make all containers of the component visible. It could be helpful when you develop and test the plugin.

then goes the layout:

Right now there are no functions. We will add them later.

and default prop values:

Handle styling

For the wrapper the overflow will be hidden because the width and the height equal 100%. To position the container inside the wrapper we will use flex layout and additional justifyContent and alignItems it will work like position: 50%, 50% and center the div inside by horizontal and vertical.

For the container inside, we will use the fit-content rule. It means that the container will take the size of the children container.

`testMode` property

This is simple. We create an object that contains styles that we will add when the test property is true.

now we’re able to see our containers. Let's add the current PanAndPinch component to the application.

To pass children in our component we don’t need to write any additional props — react will handle it for us.

App.tsx will look like this:

And when we start our plugin we will see this:

the wrapper has a slight greyish background and the movable container has a blue border.

Handle gestures and actions

Handle Pan

To handle the pan gesture we will track mouseWheel events — deltaX and deltaY this is the impulse. The strategy here is to capture this impulse and each time when we detect it we will add it with the previous value.

The pan gesture is the gesure that work on touch panels.

this is what happens when I use my MacBook trackpad

We will save the X and Y values in the spacial state transform. We will also save here scale value but this is for later.

Handle Click Drag or `Spacebar` + `Click` and mouse move

Much trickier but pretty common pattern.

here we will need to track the mouse position and translate it to the container position.

To do so, we’re adding two states:

  • spacePressed — to detect when the space bar is pressed
  • mouseKeyIsDown — we will detect when the left mouse key is pressed (also will work for laptop trackpads)

To be able to track these states we are adding onKeyDown or onKeyUp functions to the wrapper div and also don’t forget to add a tabIndex. Unfortunately without tabIndex it wouldn’t work.

Events like mouseDown and mouseUp we will track with the old fashioned way — we will use document.addEventListener in React.UseEffect

Handle Pinch effect

Terns out that it’s an easy effect until you will want to add something extra like an animation.

We will add this function into handleOnWheel

What is happening here — if the mouse wheel event is active and the ctrl key is pressed, this combination triggers pinch-to-zoom effect. Then we’re taking the wheel deltaY and apply it to the transform:scale.

Also we will set two restrictions here for the maximum transform.scale > 3and the minimum scale transform.scale < 0.3.

Three these items will cover basics, but this is not all.

Let`s also add more possibilities:

  • +/- keys to zoom
  • ctrl+mousewheel
  • on 0 key set all transforms to the initial state

Due to we need to return the container to the maximum and minimum scale in several places. We will write a function returnZoomMinOrMax

To be able to zoom in and zoom out the container we need to write a checker that will check if we click in the right key. In our case plus or minus.

And lets also make maximum and minimum zoom values as component props, to be able to change it whatever we need it.

Nerd and visual maniac