Product Manager: can you make the word cloud move?

Posted by chrbar on Mon, 20 Dec 2021 03:10:48 +0100

Product Manager: can you make the word cloud move?

This is the 25th day of my participation in the Gengwen challenge in August. See the activity details: August update challenge

☀️ preface

  • The thing is, I got the prototype of the company's data big screen some time ago, and let me make a whole page in one day.
  • After a brief look, it's a large screen of 3840 * 1840, and then several lists and legends don't look too complicated.
  • Shua! Soon, after adding a shift for a while, I adjusted the whole page and showed it to the product manager with confidence.
  • The product manager frowned: your word cloud can't move??

🌤️ Previous effects

  • Hearing this, I found that the situation was wrong. I thought about the word cloud of the prototype, and I couldn't see if he didn't ask me to move, and what I did would move!

🎢 Diagram

  • At the beginning, I used the graph diagram of echarts. The characteristic of this diagram is that at the beginning, because the repulsion force of each word will be separated from each other, there will be some dynamic effects at the beginning, but because the force guided layout will be stable after many iterations, it will not continue to move later.

 

  • Me: Yeah, I didn't lie, did I? It does move.
  • Product Manager: it doesn't work well. It doesn't have a sense of science and technology. Moreover, I want the font size to be different. I want to show the customer a version tomorrow. It's urgent. Forget it. Don't move, let him fill up the word cloud, and then the size of each word is different.

 

🎠 Word cloud picture

  • If you can't do the verb cloud, it's not easy. Just use the wordCloud diagram of echarts and configure it directly.

 

  • Product Manager: after the customer has read it, the whole is pretty good, but I still want to move the word cloud. Well, you can find a way.

 

🚄 Handwritten by yourself

  • For this word cloud, I really had a dead head at the beginning and decided to do it with ecarts, but in fact, the official website of wordCloud did not provide information, and it seems that there is really no way to make it move.
  • Think for a moment Wait a minute, word clouds should be of different sizes and colors, and then move randomly in the area. Since I'm not familiar with canvas, can I write a 2d one with js and css? To put it bluntly, a word moves randomly in a container, and then each word moves and spreads, so that it seems to work Open dry.

🚅 ToDoList

  • Prepare the container and the required configuration items
  • Generate all static word clouds
  • Let the word cloud move

🚈 Just Do It

  • Because my technology stack is Vue 2 X, so Vue 2 will be used next X syntax to share, but in fact, there is no difficulty in changing to native js. I believe you can accept it.

🚎 Prepare the container and the required configuration items

  • First, create a container to wrap the word cloud we want to load. All our next operations are carried out around this container.
<template>
  <div class="wordCloud" ref="wordCloud">
  </div>
</template>
Copy code

 

  • Because our word cloud needs different colors, we need to prepare a word list and color list, and then prepare an empty array to store the generated words.
...
data () {
    return {
            hotWord: ['Everything goes well', 'Everything goes well ', 'everything goes well and smoothly', 'everything is going smoothly', 'everything will be fine', 'good luck and happiness to you', 'be promoted step by step', 'rise step by step in the world', 'Sanyang Kaitai', 'what the heart wishes one 's hands accomplish', 'Money and treasures will be plentiful', 'Tao is not comparable', 'Family health', 'the vigor of a dragon or horse'],
            color: [
                    '#a18cd1', '#fad0c4', '#ff8177',
                    '#fecfef', '#fda085', '#f5576c',
                    '#330867', '#30cfd0', '#38f9d7'
            ],
            wordArr: []
    };
}
...
Copy code
  • These words are prepared to say to you who are reading the article now ~ if you think I'm right, you might as well give a praise after reading the article~
  • OK, no kidding. Now the preparations are finished and we start to generate our word cloud.

🚒 Generate all static word clouds

  • If we want to fill a container with words, according to the normal logic of our cut diagram, each word accounts for a span, then it is equivalent to a div with n (number of hotword) words, that is, the container has a corresponding number of span tags.
  • If you need different colors and sizes, add different styles to the span label.
...
mounted () {
        this.init();
},
methods: {
        init () {
            this.dealSpan();
        },
        dealSpan () {
            const wordArr = [];
            this.hotWord.forEach((value) => {
                    // Set the font color and size according to the number of word clouds generated
                    const spanDom = document.createElement('span');
                    spanDom.style.position = 'relative';
                    spanDom.style.display = "inline-block";
                    spanDom.style.color = this.randomColor();
                    spanDom.style.fontSize = this.randomNumber(15, 25) + 'px';
                    spanDom.innerHTML = value;
                    this.$refs.wordCloud.appendChild(spanDom);
                    wordArr.push(spanDom);
            });
            this.wordArr = wordArr;
        },
        randomColor () {
            // Get random color
            var colorIndex = Math.floor(this.color.length * Math.random());
            return this.color[colorIndex];
        },
        randomNumber (lowerInteger, upperInteger) {
            // Gets a random number that contains the minimum and maximum values.
            const choices = upperInteger - lowerInteger + 1;
            return Math.floor(Math.random() * choices + lowerInteger);
        }
}
...
Copy code
  • We traverse the hotWord hot word list, generate a span tag every time there is a word, and set different random colors and sizes using randomColor() and randomSize() respectively.
  • Finally, add these span s into the div container in turn, and then it will be like this after completion.

 

🚓 Let the word cloud move

  • After words are added, we need to make them move. So how to move them? We naturally think of the translateX and translateY attributes of transform. First, let a word move first, and then apply this method to all.

First move the x-axis

  • How do you move? What we need to do now is an infinite loop, that is, an element moves infinitely. Since it is infinite, can we use a timer in js? It is too laggy, but if you have a large number of words, your computer will explode. On the other hand, the key to compiling the animation cycle is to know how long the delay time is, if it is too long or too short, it is not suitable, so there is no timer.
  • Then I accidentally found window requestAnimationFrame is an API. requestAnimationFrame does not need to set a time interval.

requestAnimationFrame gathers all DOM operations in each frame and completes them in one redraw or reflow. The time interval of redraw or reflow closely follows the refresh rate of the browser. Generally speaking, this rate is 60 frames per second.

  • That is to say, when we circularly move an element infinitely on the x-axis or y-axis, assuming that it moves to the right by 10px per second, its translateX is accumulated by 10px. This is true for each element, so we need to add an attribute to the span element to represent its position.
data () {
    return {
        ...
        timer: null,
        resetTime: 0
        ...
    };
}
methods: {
    init () {
            this.dealSpan();
            this.render();
    },
    dealSpan () {
            const wordArr = [];
            this.hotWord.forEach((value) => {
                    ...
                    spanDom.local = {
                            position: {
                                    x: 0,
                                    y: 0
                            }
                    };
                    ...
            });
            this.wordArr = wordArr;
    },
    render () {
            if (this.resetTime < 100) {
                    //Prevent "stack overflow"
                    this.resetTime = this.resetTime + 1;
                    this.timer = requestAnimationFrame(this.render.bind(this));
                    this.resetTime = 0;
            }
            this.wordFly();
    },
    wordFly () {
            this.wordArr.forEach((value) => {
                    //Add 1 for each cycle
                    value.local.position.x += 1;
                    // Add animation transition to each word cloud
                    value.style.transform = 'translateX(' + value.local.position.x + 'px)';
            });
    },
},
destroyed () {
        // Components are destroyed and closed for scheduled execution
        cancelAnimationFrame(this.timer);
},
Copy code
  • At this time, we add a local attribute to each element, which has its initial position. Whenever we execute requestAnimationFrame, its initial position is + 1, and then give this value to translateX. In this way, each cycle is equivalent to moving 1px. Now let's see the effect.

 

Adjustment range

  • well! Good guy, it's moving, but why is it too much?
  • We found that each translateX was + 1, but we didn't give him a stop range, so we need to give him a step to turn around when he reaches the edge of the container.
  • So how do you make him turn around? Since we can make it move 1px to the right every time, can we detect that when its X-axis position is greater than the position of the container, when the x-axis position is less than the position of the container, and change the direction, we only need to use positive and negative numbers to judge.
init () {
        this.dealSpan();
        this.initWordPos();
        this.render();
},
dealSpan () {
        const wordArr = [];
        this.hotWord.forEach((value) => {
            ...
            spanDom.local = {
                    position: {
                            // Location
                            x: 0,
                            y: 0
                    },
                    direction: {
                            // Direction positive number to right negative number to left
                            x: 1,
                            y: 1
                    }
            };
            ...
        });
        this.wordArr = wordArr;
},
wordFly () {
        this.wordArr.forEach((value) => {
            // Set the change direction when the motion direction is greater than or less than the boundary
            if (value.local.realPos.minx + value.local.position.x < this.ContainerSize.leftPos.x) {
                    value.local.direction.x = -value.local.direction.x;
            }
            if (value.local.realPos.maxx + value.local.position.x > this.ContainerSize.rightPos.x) {
                    value.local.direction.x = -value.local.direction.x;
            }
            //Move 1 unit to the right every time. When the direction is negative, it is - 1 unit, that is, move 1 unit to the left
            value.local.position.x += 1 * value.local.direction.x;
            // Add animation transition to each word cloud
            value.style.transform = 'translateX(' + value.local.position.x + 'px)';
        });
},
initWordPos () {
        // Calculate the real position of each word and the position of the container
        this.wordArr.forEach((value) => {
            value.local.realPos = {
                    minx: value.offsetLeft,
                    maxx: value.offsetLeft + value.offsetWidth
            };
        });
        this.ContainerSize = this.getContainerSize();
},
getContainerSize () {
        // Determine container size and control word cloud position
        const el = this.$refs.wordCloud;
        return {
            leftPos: {
                    // Position of left side and top of container
                    x: el.offsetLeft,
                    y: el.offsetTop
            },
            rightPos: {
                    // Position of the right side and bottom of the container
                    x: el.offsetLeft + el.offsetWidth,
                    y: el.offsetTop + el.offsetHeight
            }
        };
}
Copy code
  • At the beginning, we first use initWordPos to calculate the current position of each word and save its position, and then use getContainerSize to get the leftmost, rightmost, uppermost and lowermost position of our external container and save it.
  • Add an attribute direction , direction to each span. When the direction is negative, it goes to the left and the direction is regular to the right. I wrote the comments on the code. If you don't clear them, you can have a look.
  • In other words, our word cloud will jump across the container repeatedly. Let's see the effect.

 

Random displacement

  • Very good, is the effect we want!!!
  • Of course, we can't write dead displacement every time, only displacement 1px. If we want to achieve that messy beauty, we need to do a random displacement.
  • How to do random displacement? It can be seen that our words are actually doing uniform linear motion, and do you remember the formula of uniform linear motion?
  • If you don't remember, I suggest you go back to the physics book ~ the displacement formula of uniform linear motion is {x=vt
  • This x is the displacement we need, and we don't care about this T, because I said above that this requestAnimationFrame will help us set the time, so we just need to control that the initial velocity of v is random.
dealSpan () {
        const wordArr = [];
        this.hotWord.forEach((value) => {
            ...
            spanDom.local = {
                    velocity: {
                            // Initial velocity of each displacement
                            x: -0.5 + Math.random(),
                            y: -0.5 + Math.random()
                    },
            };
            ...
        });
        this.wordArr = wordArr;
},
wordFly () {
        this.wordArr.forEach((value) => {
            ...
            //Using the formula x=vt
            value.local.position.x += value.local.velocity.x * value.local.direction.x;
            ...
        });
},
Copy code
  • We give each word span element an initial speed, which can be - or + to move left or right. When we deal with this translateX, it will be handled randomly. Now let's see the effect.

 

Perfect y-axis

  • Now the x-axis has been completed as we think. To make the word clouds move up, down, left and right, we need to match the y-axis according to the x-axis method.
  • Due to the length of the code, I won't release it. I will give the source code below. If you are interested, you can download it ~ let's take a look at the finished product directly! Xiao Lu, thank you for your reading, so I'm here to wish you:

 

Topics: Javascript echarts