The introduction

In the age of touch screens, humanized gestures have permeated every part of our lives. Modern applications pay more and more attention to the interaction and experience with users. Gesture is the most direct and effective way of interaction. A good gesture interaction can reduce the user’s cost and process, and greatly improve the user experience.

Recently, the company has a high demand for gestures in a number of projects. The existing gesture library cannot be fully covered, so it has developed a lightweight and easy-to-use mobile gesture library. This post explains the principles of common gestures on mobile devices and the mathematics used to learn from the front end. Hope to have a little bit of inspiration for you, but also look forward to the gods to point out the shortcomings and even mistakes, Thanksgiving.

Here are five gestures that are commonly used in projects:

  • Drag:drag
  • Double finger zoom:pinch
  • Double finger rotation:rotate
  • One-finger zoom:singlePinch
  • One-finger rotation:singleRotate

Tips: Because tap and Swipe are included in many basic libraries, they are not included for portability, but can be extended if necessary;

Realize the principle of

As we all know, all gestures are wrapped on top of the browser’s native events touchStart, TouchMove, Touchend, and TouchCancel, so the idea is to wrap handleBus through a repository of independent event callbacks. Then in the original touch event in the condition of the time triggered and outgoing calculated parameter value, complete the operation of the gesture. The realization principle is relatively simple and clear, don’t worry, let’s first sort out some used mathematical concepts and combine the code to apply mathematics to practical problems, the mathematics part may be a bit boring, but I hope you stick to it, I believe it will benefit a lot.

Basic mathematical knowledge function

Our common coordinate system belongs to linear Space, or Vector Space. This space is a set of points and vectors;

Point (Point)

It can be interpreted as our coordinate points, such as origin O(0,0),A(-1,2). The coordinates of touch points can be obtained through the touches of the native event object, and the parameter index represents the touch point.

Vector (Vector)

Is A line segment with both magnitude and direction in the coordinate system. For example, the arrow line segment from the origin O(0,0) to point A(1,1) is called vector A, then A =(1-0,1-0)=(1,1);

As shown in the figure below, I and j vectors are called the unit vector of the coordinate system, also known as the basis vector. The unit of our common coordinate system is 1, that is, I =(1,0); J = (0, 1);

Get vector function:

Vector module

On behalf of the length of the vector, remember to | a |, is a scalar, only size, no direction;

The geometric meaning represents the hypotenuse of a right triangle with x and y as sides, calculated by the Pythagorean theorem;

GetLength function:

The dot product of vectors

Vectors also have operable properties. They can perform addition, subtraction, multiplication, dot product, and cross product operations. Next, the concept of dot product we use, also known as dot product, is defined as the formula:

When a = (x1, y1), b = (x2, y2), is a, b = | a |, | | b, cos theta = x1, x2 + y1, y2;

Collinear theorem

Collinear, that is, two vectors are in parallel state, when a=(x1,y1),b=(x2,y2), there is a unique real number λ, such that a=λb, after substituting punctuation, it can be obtained that x1·y2= y1·x2;

Therefore, when x1·y2-x2·y1>0, the slope ka > KB, so b vector is clockwise rotation relative to A vector; otherwise, it is counterclockwise;

Rotation Angle

Using the dot product formula, we can deduce the Angle between the two vectors:

Cosine theta = (x1, x2 + y1, y2)/(| | | | b, a);

Then, we can determine the direction of rotation by collinear theorem, and the function is defined as:

Matrices and Transformations

Since the most essential feature of space is that it can accommodate motion, in linear space,

Vectors are used to describe objects, and matrices are used to describe the motion of objects;

How does a matrix describe motion?

We know that a vector can be determined by the basis vector of a coordinate system, for example, a=(-1,2). The basis vectors we usually agree on are I =(1,0) and j =(0,1); Therefore:

A = 1 + 2 j I = 1 + 2 * * (1, 0) (0, 1) = (1 + 0, 0, 2) = (1, 2);

The matrix transformation, in fact, is through the matrix transformation of the basis vector, so as to complete the transformation of the vector;

For example, the a vector is transformed through the matrix (1,-2,3,0), and the basis vector I is transformed from (1,0) to (1,-2) and j is transformed from (0,1) to (3,0)

A = 1i + 2j = -1(1,-2) + 2(3,0) = (5,2);

As shown in the figure below: Figure A represents the coordinate system before transformation. At this time, A =(-1,2). After the matrix transformation, the transformation of basis vectors I and j causes the transformation of the coordinate system, which becomes the following figure B.

In fact, the relation between vector and coordinate system remains unchanged (a = -1i+2j). It is the basis vector that causes the coordinate system to change, and then the coordinate system uses the relation to cause the change of vector.

Combining with the code

In fact, the CSS transform and other transformations are implemented by matrices. The translate/rotate syntax is a kind of encapsulated syntax candy, which is easy to use. At the bottom, all transformations are converted into matrices. For example transform:translate(-30px,-30px) will be translated into transform: matrix(1,0,0,1,30,30);

Normally in a two-dimensional coordinate system, only a 2X2 matrix is sufficient to describe all transformations. However, since CSS is in a 3D environment, a 3X3 matrix is used in CSS, which is expressed as:

0,0,1 in the third row is the default z-axis parameter. In this matrix, (a,b) is the I basis of the coordinate axes, and (c,d) is the J basis,e is the offset of the x axis, and f is the offset of the y axis. Translate (-30px,-30px) ==> matrix(1,0,0,1,30,30)~ ==> matrix(1,0,0,1,30)~

All transform statements are transformed as follows:

// Offset occurs, but the basis vector remains the same; Transform :matrix(1,0,0,1,x,y); transform:matrix(1,0,0,1,x,y); Transform: the rotate (theta deg) = = > transform: matrix (cos (theta PI / 180), sin (theta PI / 180), - sin (theta PI / 180), cos (theta PI / 180), 0, 0) / / base vector amplification and direction; The transform: scale (s) = = > transform: matrix (s, 0, 0, s, 0, 0)Copy the code

Translate /rotate/scale syntax is very powerful, which makes our code more readable and easy to write, but Matrix has more powerful transformation features, through matrix, transformation can occur in any way, such as mirror symmetry, The transform matrix (1,0,0,1,0,0);

MatrixTo

While matrix is powerful, it is not very readable, and we write through the translate/rotate/scale attribute, while the transform read through getComputedStyle is matrix:

Transform :matrix(1.41421, 1.41421, -1.41421, 1.41421, -50, -50);

How has this element changed? . This is a face of confusion. -_ – | | |

Therefore, we had to have a way to translate the matrix into the more familiar translate/rotate/scale method, and once we understood how it worked, we could start performing

As we know, the first four parameters are influenced by both rotate and scale and have two variables, so we need to list the two inequalities using the first two parameters according to the above transformation method:

Cos (theta PI / 180) * s = 1.41421;

Sin (theta PI / 180) * s = 1.41421;

By dividing these two inequalities, we can easily solve for θ and s. The function is as follows:

Principle of gestures

Next, we will use the above function in the actual environment to simulate the operation of gesture through the way of illustration, and briefly explain the principle of gesture calculation. Hopefully, once you understand the basics, you’ll be able to create more cool gestures like the ones we use on the MAC trackpad.

The following legend:

1. The touch point of the finger;

The dashed line segment between the two dots: represents the vector formed by the two-finger operation;

Vector A/point A: represents the initial vector/initial point acquired during touchStart;

B vector/b point: represents the real-time vector/real-time point acquired during Touchmove;

The formula at the bottom of the axis represents the value to be computed;

Drag(Drag event)

The figure above simulates the dragging gesture, moving from point A to point B. What we want to calculate is the offset of this process.

So we record the coordinates of the initial point A in touchStart:

// get the initial point A;
let startPoint = getPoint(ev,0);
Copy the code

Then get the current point in the TouchMove event and calculate △x and △y in real time:

// Get the initial point B;
let curPoint = getPoint(ev,0);

// Through A and B, calculate the displacement increment in real time, trigger the drag event and transmit the parameter;
_eventFire('drag', {
    delta: {
        deltaX: curPoint.x - startPoint.x,
        deltaY: curPoint.y - startPoint.y,
    },
    origin: ev,
});
Copy the code

The fire function iterates through the repository of callbacks that execute the Drag event.

Two fingers Pinch

The figure above is the simulation of two-finger scaling, which is enlarged from vector A to vector B. The scaling value can be obtained by calculating the modulus of vector A in the initial state and the modulus of vector B obtained in TouchMove:

// Calculate the vector modulus of the initial two fingers in touchStart;
let vector1 = getVector(secondPoint, startPoint);
let pinchStartLength = getLength(vector1);

// Calculate real-time two-finger vector mode in touchMove;
let vector2 = getVector(curSecPoint, curPoint);
let pinchLength = getLength(vector2);
this._eventFire('pinch', {
	delta: {
	    scale: pinchLength / pinchStartLength,
	},
	origin: ev,
});
Copy the code

Rotate(double finger rotation)

To start with the two-finger vector A and rotate to vector B, θ is the value we need, so we can calculate the rotation Angle simply by using the getAngle function we built above:

// a vector;
let vector1 = getVector(secondPoint, startPoint);

// b vector;
let vector2 = getVector(curSecPoint, curPoint);

// Trigger event;
this._eventFire('rotate', {
    delta: {
        rotate: getAngle(vector1, vector2),
    },
    origin: ev,
});
Copy the code

singlePinch

Unlike the gesture above, single-finger zooming and single-finger rotation require multiple unique concepts:

Operator: The element to be operated on. In fact, the above three gestures do not care about the operation element, because the correct parameter value can be calculated solely by the gesture itself, while the single-finger zoom and rotation need to be calculated depending on the reference point of the operation element (the center point of the operation element).

Button: Because the one-finger gesture is in conflict with the drag gesture, a special interaction is needed to distinguish it. Here, it is distinguished by a specific area, similar to a button. When you operate on the button, you zoom or rotate with one finger, and outside the button area, you drag normally. This is a user is easy to accept and experience better operation mode;

In the figure, the single finger of vector A is enlarged to vector B, and the center of the operator element (square) is enlarged. At this time, the scale value is the modulus of vector B/the modulus of vector A.

// Calculate the reference point of the single finger operation, obtain the center point of the operator;
let singleBasePoint = getBasePoint(operator);

// Calculate the initial vector mode in touchstart;
let pinchV1 = getVector(startPoint,singleBasePoint);
singlePinchStartLength = getLength(pinchV1);

// Calculate real-time vector mode in touchMove;
pinchV2 = getVector(curPoint, singleBasePoint);
singlePinchLength = getLength(pinchV2);

// Trigger event;
this._eventFire('singlePinch', {
    delta: {
        scale: singlePinchLength / singlePinchStartLength,
    },
    origin: ev,
});
Copy the code

SingleRotate (One-finger rotation)

Combining one-finger scaling and two-finger rotation, it’s easy to know that θ is the rotation Angle we need;

// Get the initial and real-time vectors
let rotateV1 = getVector(startPoint, singleBasePoint);
let rotateV2 = getVector(curPoint, singleBasePoint);

// getAngle gets the rotation Angle and triggers the event;
this._eventFire('singleRotate', {
    delta: {
        rotate: getAngle(rotateV1, rotateV2),
    },
    origin: ev,
});
Copy the code

Acceleration of

Since the TouchMove event is a high-frequency real-time triggering event, a drag operation actually triggers N touchmove events, so the calculated value is only an increment, that is, it represents the value increased by a TouchMove event. It only represents a small segment of value, not the final value. Therefore, a location data needs to be maintained externally by mtouch.js, similar to:

// Real location data;
let dragTrans = {x = 0,y = 0};

// Add the increment of deltaX and deltaY passed by mtouch;
dragTrans.x += ev.delta.deltaX;
dragTrans.y += ev.delta.deltaY;

// Manipulate elements directly by transform;
set($drag,dragTrans);
Copy the code

The initial position

To maintain the external position data, if the initial value is directly 0 like the above, the element with the transform attribute set by CSS will not be correctly identified, which will cause the operation element to jump back to the point of (0,0) immediately at the beginning. Therefore, we need to initially obtain the real position value of an element before maintenance and operation. In this case, we need to use the getComputedStyle method and the matrixTo function mentioned above:

// Get the CSS transform property.
/ / the transform matrix (1.41421, 1.41421, 1.41421, 1.41421, and 50, and 50);
let style = window.getComputedStyle(el,null);
let cssTrans = style.transform || style.webkitTransform;

// Perform the conversion according to the rules to get:
let initTrans = _.matrixTo(cssTrans);

// {x:-50,y:-50,scale:2,rotate:45};
Transform :translate(-50px,-50px) scale(2) rotate(45deg);

Copy the code

conclusion

So far, I believe you have a basic understanding of the principle of gesture, based on these principles, we can encapsulate more gestures, such as double click, long press, sweep, and even more cool three-finger, four-finger operation, so that the application has more human characteristics.

Based on the above principles, I have encapsulated a few common tools:

Tips: Because it is only for mobile terminal, you need to open demo on mobile device, or open Mobile debug mode on PC!

  1. Mtouch. Js: gesture library for mobile terminal, encapsulating the above five gestures, simplified API design, covering common gesture interaction, based on which can also be easily extended. demo github

  2. Touchkit.js: MTouch based package of a more business-close toolkit, can be used to create a variety of gesture operations, one-click open, one-stop service. demo github

  3. McAnvas. Js: Based on canvas open minimalist API to achieve picture < paragraph text > < mixed text > < cropping > < pan > < rotate > < zoom > < watermark add > one-click export, etc. demo github

Welcome to pay attention to the public number and I in-depth face base. 🤣