There is a need

There is a need, shake the phone to move the ball to give the cat an edge.

Pull a bunch of people to evaluate, see how to do it.

See this demand is really leng next, and then carefully think about it, it seems that there is no way.

A feature that had not been used for a long time came to mind, “gyroscope”.

What is a gyroscope

It’s a directional sensor that picks up data when it detects a change in direction.

A change of direction event is an event triggered when a mobile device, such as a mobile phone, reverses direction.

There’s a lot you can do with gyroscope on mobile. He has an API in our Web.

The Window object can listen for DeviceOrientation events to call the gyroscope sensor on the mobile.

But we mainly use it to judge the direction in this paper.

Just stroke the cat. Rules – only shake the phone

Analysis of a wave

First, shake the phone and move the brush stroke. The fewer words, the bigger the matter.

We can get direction with the gyroscope, but how do we stroke?

Can we draw a path beforehand and then move the brush to draw a path?

It looks like it could, but here’s the thing,

So how do we plot this path?

So how do we store this path?

How can you tell which way a path is moving by shaking your phone?

Practice is the only criterion for testing code

Let’s start by creating a sketchpad tool that can manually stroke the cat.

Create a simple printboard tool

First, we need an artboard to draw the stroke path.

Here I use React + Pixijs to build the artboard.

I’m not going to go into the structure of the project, just get started. Let’s take a look at the finished drawing board.

Function introduction:

  1. Set the brush width βœ…
  2. Set the brush color βœ…
  3. Draw base map support network picture and local picture drag βœ…
  4. Display and copy the path array βœ…
  5. Undo βœ…
  6. Remove βœ…
  7. Demo βœ…

Set the stage

Use React to build the page and canvas stage. We need to draw paths on the stage.

Interested students can view the source code

Here I used a 750*1624 stage size for mobile.

/** Initialize the stage */
onInitStage = () = > {
    const _c = this.stageCancas.current;
    // Width and height of design draft
    const width = 750;
    const height = 1624;
    const app = new PIXI.Application(width, height, {
        view: _c,
        backgroundColor: 0x1099bb
    });
    app.ticker.add((deltaTime) = >{});this.stage = app.stage;

    / / reproduction
    const barrel = this.stage.addChild(PIXI.Sprite.from("./barrel.png"));
    barrel.interactive = true;
    this.barrel = barrel;

    / / the ball
    this.ball = this.stage.addChild(PIXI.Sprite.from("./ball.png"));
    this.ball.anchor.set(this.ball.width / 2.this.ball.height / 2)

    this.onAddEvents(barrel);

    / / draw the line
    this.line = new PIXI.Graphics();
    this.stage.addChild(this.line);
}

Copy the code

Draw paths on the canvas

Draw different paths on the canvas by listening for mouse press, move, and lift events.

Record the first stroke of the first step when the mouse is pressed down.

this.line.lineStyle(this.state.lineWidth, this.state.color.replace("#"."0x"), 1);
this.line.moveTo(_x, _y);

// First point
this.currentPoints = [];
this.currentPoints.push({ x: _x, y: _y });
Copy the code

As the mouse moves, each point is stored. Here we can set sparse or dense collection points.

// Whether to draw points
let disX = Math.abs(point.x - this.lastPoint.x);
let disY = Math.abs(point.y - this.lastPoint.y);
let dis = Math.sqrt(disX * disX + disY * disY);

if (dis >= 2) {
    this.line.lineStyle(this.state.lineWidth, this.state.color.replace("#"."0x"), 1);
    this.line.lineTo(point.x, point.y);

    this.lastPoint = point;
    this.currentPoints.push({
        x: Math.floor(point.x),
        y: Math.floor(point.y)
    });
}
Copy the code

When the mouse is up, we are finished drawing the one-step command, storing all the points of the one-step drawing command.

/ / every step
this.step++;
this.stepPoints[this.step] = this.currentPoints;
Copy the code

Then output all the points of all the drawing steps and display them in the code tag.

/** outputs the array */
onGetOutPutArr = () = > {
    let outArr = [];
    for (let item in this.stepPoints) {
        outArr = [...outArr, ...this.stepPoints[item]]
    }
    this.setState({
        listTextArea: outArr.length > 0 ? JSON.stringify(outArr) : "Path array..."
    });
}
Copy the code

This way, we can use the mouse to draw on the palette, and each path is recorded.

It is convenient for us to draw in the formal project later.

Add some functionality to the artboard

  1. Add stroke width
  2. Add stroke color
state = {
        listTextArea: "Path array...".color: "#ffffff".// Brush color
        lineWidth: 8 // Brush width
    }
Copy the code

Here I use react-color to build the color picker.

import { ChromePicker } from 'react-color';
Copy the code

It worked really well.

  1. Add the replication path function

Replication is implemented through the execCommand function.

<input type="text" readOnly="readonly" ref={this._select} value={listTextArea} />
Copy the code
this._select.current.select();
document.execCommand("Copy");
Copy the code
  1. Add undo

Command revocation is controlled through the control step step.

Delete the corresponding step in the object, and then redraw the path using the brush.

/** 撀销 */
onUndoLastStep = () = > {
    delete this.stepPoints[this.step];
    this.step--;
    // Redraw
    this.line.clear();
    for (let item in this.stepPoints) {
        let lineList = this.stepPoints[item];
        if (lineList && lineList.length > 0) {
            this.line.lineStyle(this.state.lineWidth, this.state.color.replace("#"."0x"), 1);
            this.line.moveTo(lineList[0].x, lineList[0].y);
            lineList.forEach(it= > {
                this.line.lineTo(it.x, it.y); }}})this.onGetOutPutArr();
}
Copy the code
  1. Add clear functionality

Use the clear() method directly here.

Remember the cleanup steps and coordinate points.

 /** 清陀 */
onClickClear = () = > {
    this.line.clear();
    this.stepPoints = {};
    this.currentPoints = [];
    this.onGetOutPutArr();
}
Copy the code
  1. Add demo features

So here I have a little ball going along the path.

/ * * * / demonstration
onShowDemo = () = > {
    let count = 0;
    this.stage.addChild(this.ball);
    let outArr = [];
    for (let item in this.stepPoints) {
        outArr = [...outArr, ...this.stepPoints[item]]
    }
    let timer = setInterval(() = > {
        move();
    }, 30);
    / / move
    const move = () = > {
        count++;
        if (count >= outArr.length) {
            clearInterval(timer);
            return;
        }
        this.ball.position.set(outArr[count].x, outArr[count].y)
    }
}

Copy the code
  1. Add drag and drop local image function

So here we use onDragEnter onDragOver onDrop, these three trigger events.

After dragging the local image onto the artboard, we render the image onto the stage.

/** Process the rendered image file */
processFiles = (files) = > {
    var file = files[0];
    var reader = new FileReader();
    let self = this;
    reader.onload = function (e) {
        let bg = new Image();
        bg.src = e.target.result;
        / / to draw
        self.barrel.texture = PIXI.Texture.from(bg);
    };
    // Read the image
    reader.readAsDataURL(file);
}
Copy the code

So we have our sketchboard.

So let’s see what happens. I wrote “cat” on the board.

Mobile development

Foreplay is too long. Next comes the main requirement.

Start by drawing a background image with a cat on it.

The demand requires us to shake the phone, move the ball and stroke the cat.

To illustrate, I’ll draw a dashed line for the path.

this.realLine = new PIXI.Graphics();
this.realLine.lineStyle(8.0xf4813e.1);
this.realLine.moveTo(firstList[0].x, firstList[0].y);
app.stage.addChild(this.realLine);

firstList.forEach((item, index) = > {
    if (index % 3= =0) {
        if (index > 1)
            this.line.moveTo(firstList[index - 1].x, firstList[index - 1].y)
        this.line.lineTo(item.x, item.y); }});Copy the code

Then draw a ball.

And then we’re going to move this ball around.

Use a gyroscope

Use the gyroscope in the mobile terminal H5 page to obtain the movement direction of the ball.

Note that gyroscope calls in ios require authorization. We also need to click to trigger the authorization popover.

The following is a compatible way of writing a gyroscope license:

// iOS 13+
if (window.DeviceOrientationEvent ! = =undefined && typeof window.DeviceOrientationEvent.requestPermission === 'function') {
    window.DeviceOrientationEvent.requestPermission()
        .then(function (response) {
            if (response == 'granted') {
                this.onTestGyro();
            }else{

            }
        }).catch(function (error) {
            console.log("error", error);
        });

} else {
    console.log("Start")
    this.onTestGyro();
}
Copy the code

Once the authorization is successful, we can get the direction data.

Notice that the gyroscope returns three parameters: alpha rotates about the z axis, beta rotates about the x axis, and Gamma rotates about the y axis.

I’m only dealing with four directions here.

/** direction right upper left upper right lower left lower */
const DIR = {
    RT: "RT".LT: "LT".LB: "LB".RB: "RB"
}
Copy the code

Direction is confirmed by beta and gamma, since only planar motion is required, not 3D motion. So select only beta and gamma.

When e.eta is in the range [-180,0], the direction is top, otherwise the direction is bottom;

Where LLDB amma is in the range [-180,0], the direction is left; otherwise, it is right.

Use the code to determine the following:

window.addEventListener("deviceorientation".(e) = > {
    let _dir = "";
    if (e.beta >= -180 && e.beta <= 0) {
        _dir = _dir + "T";
    } else {
        _dir = _dir + "B";
    }
    if (e.gamma >= -180 && e.gamma <= 0) {
        _dir = "L" + _dir;

    } else {
        _dir = "R" + _dir;
    }
    / / the console. The info (" gyroscope, "` x: ${e.a lpha}, y: ${e.b eta}, z: ${um participant amma} `, _dir);
    this.curerentDir = DIR[_dir];
}, false);
Copy the code

So you can get the direction change in real time.

Move the ball to stroke the cat

So how are we going to move the ball, what we’re going to do here is make our path array a two-way circular list.

/ / list
var Node = function (element) {
    this.element = element;
    this.next = null;
    this.prev = null;
};
Copy the code

Add a method to the list where you can add elements:

this.append = function (element) {
    var node = new Node(element),
        current,
        previous;

    if(! head) { head = node; tail = node; head.prev = tail; tail.next = head; }else {
        current = head;

        while(current.next ! == head) { previous = current; current = current.next; } current.next = node; node.next = head; node.prev = current; }; head.prev = node; length++;return true;
};

Copy the code

After implementing a bidirectional circular list, we convert stroke paths to lists.

/** Convert coordinate array to bidirectional list */
dealArr2List = () = > {
    const arr = firstList;
    const list = new get2LoopList();
    arr.forEach((item, index) = > {
        list.append(item);
    });

    this.currentPoint = list.getHead();
}

Copy the code

It has the current point, the previous point, and the next point. We change the position of the ball every frame.

At the same time, the direction of the previous point and the next point relative to the current point can be obtained by comparing the points.

If the direction is consistent with the direction obtained by the gyroscope, then move.

/** determine the direction */
onCheckDir = (element = {}, nextElement = {}) = > {
    let _dir = "", disX = element.x - nextElement.x, disY = element.y - nextElement.y;
    // Determine the general direction
    if (Math.abs(disX) > Math.abs(disY)) {
        // Judge left and right
        if (disX > 0) {
            _dir = "L";

        } else {
            _dir = "R"; }}else {
        // judge up and down
        if (disY > 0) {
            _dir = "T";
        } else {
            _dir = "B"; }}return this.curerentDir.indexOf(_dir) > -1 ? true : false;
}
Copy the code

Now that we know how to move the ball, we need to move the ball and map its path.

 Mobile / * * * /
onMoveDir = () = > {
    const { element, next = {}, prev = {} } = this.currentPoint;
    const nextElement = next.element;
    const prevElement = prev.element;

    if (this.onCheckDir(element, nextElement)) {
        this.ball.x = nextElement? .x;this.ball.y = nextElement? .y;this.realLine.lineStyle(8.0xf4813e.1);
        this.realLine.lineTo(nextElement? .x, nextElement? .y);this.currentPoint = next;

    } else if (this.onCheckDir(element, prevElement)) {
        this.ball.x = prevElement? .x;this.ball.y = prevElement? .y;this.realLine.lineStyle(8.0xf4813e.1);
        this.realLine.lineTo(prevElement? .x, prevElement? .y);this.currentPoint = prev; }}Copy the code

And then we’re done.

Isn’t that easy?

The moment that receives this kind of demand, the person is meng, reason slowly once, in fact wrong CP, they are not easy.

Let’s take a look at the demo:

Here the video GIF is too big, so I took the middle part to disguise it. The phone shook and the control ball traced a border to the cat.

You can see the source code here

conclusion

A word of demand, I made a drawing board, but also made a demo.🐢🐢🐢

At the same time, the application of gyroscope in mobile terminal is investigated, so you can’t look at CP with colored glasses. After all, they can give you a lot of strange ideas.

Even if they’re love-hate ideas.

What strange need have you encountered?

Welcome to pat brick pointing, the author’s skill is still shallow, if there is improper place please be corrected.

reference

deviceorientation

The article is shallow, hope you don’t hesitate to your comments and praise ~

Note: this article is the author’s painstaking work, reprint must be declared