Turn the mouse pointer into a little rabbit

I am participating in the Mid-Autumn Festival Creative Submission Contest. Please see: Mid-Autumn Festival Creative Submission Contest for details.

preface

Mid-Autumn Festival soon, the nuggets began to live, happen to be ready to do recently tools will involve changes in the mouse pointer, it is convenient to write a demo dawdling activities.

By the way, I wish you a happy Mid-Autumn Festival 🎑 in advance.

Of course, this mouse pointer change only works in Web applications

Results the following

emmmmm… Gifs take a long time and need to wait for the effect to come out

Experience on the code

Just run the following mysterious code in DevTools, see the source code here

const script = document.createElement('script')
script.src = 'https://img.cdn.sugarat.top/demo/js-sdk/zq-rabbit/0.0.2/index.js'
document.body.append(script)
Copy the code

Add this part of the code to the target page

Implementation approach

There are two elements in the driven graph:

  1. When the mouse moves, the mouse is replaced with the Jade Rabbit
  2. The tail of the jade Rabbit follows a string of moon cakes 🥮

Let’s take a look at some of the issues involved in development through QA.

Gets the position of the mouse pointer

By listening to the Mousemove event on the window, you can get the position parameter when the mouse moves

Hide the original pointer

The CSS has a property called CURSOR that can be used to set the type of pointer. We set the CURSOR of the DOM (event.target) that captures the event to None

Implement pointer changes

With these two problems solved, we can replace our pointer by creating a simple DOM element that updates the position of our DOM element in real time by getting the mouse position in real time

Realize mouse track

Each period of time (such as 30ms) record the position of the mouse, and then draw the track with the moon cake 🥮 using the same logic as drawing the pointer

This section describes only the problems encountered in the early stages of development, but there are other problems that will be covered in the detailed implementation section below

Yutu pointer implementation

Listen for the Mousemove event to get the pointer position relative to the top and left of the screen

window.addEventListener('mousemove'.function (e) {
    const { clientX, clientY } = e
})
Copy the code

Create an element, set its background to Yutu, insert it into the main document, and initialize some position/shape related CSS properties.

const size = '30px'
function createCursor() {
    const cursor = h()
    cursor.id = 'cursor'

    addStyles(cursor, `
    #cursor{
        background-image:url(https://img.cdn.sugarat.top/mdImg/MTYzMTMyNDYwNTgzMQ==631324605831);
        width:${size};
        height:${size};
        background-size:${size} ${size};
        position:fixed;
        display:none;
        cursor:none;
        transform: translate(-30%, -20%);
    }
    `)
    document.body.append(cursor)
    return cursor
}
const cursor = createCursor()

// Tool methods
function addStyles(target, styles) {
    const style = document.createElement('style')
    style.textContent = styles
    target.append(style)
}

function h(tag = 'div') {
    return document.createElement(tag)
}
Copy the code

Write a method called refreshCursorPos to update the yutu position and, after some time, restore the pointer to its original state

function refreshCursorPos(x, y) {
    cursor.style.display = 'block'
    cursor.style.cursor = 'none'
    cursor.style.left = `${x}px`
    cursor.style.top = `${y}px`

    // Hide after some time
    if (refreshCursorPos.timer) {
        clearTimeout(refreshCursorPos.timer)
    }
    refreshCursorPos.timer = setTimeout(() = > {
        cursor.style.display = 'none'
    }, 500)}Copy the code

Combined with the previous method, and the pointer of the target element is hidden for a period of time, hiding and recovery here with WeakMap to do an auxiliary, storage node and timer mapping relationship, do a simple shake prevention

const weakMap = new WeakMap(a)window.addEventListener('mousemove'.function (e) {
    const { clientX, clientY } = e

    // Hide the pointer to the element that captured the Mousemove event and restore it after some time
    e.target.style.cursor = 'none'
    let timer = weakMap.get(e.target)
    if(timer){
        clearTimeout(timer)
    }
    timer = setTimeout(() = >{
        e.target.style.cursor = 'auto'
    },500)
    weakMap.set(e.target,timer)
    
    // Update yutu location
    refreshCursorPos(clientX, clientY)
})
Copy the code

You think it’s over here? Of course not, there is a problem, your yutu pointer does not work properly, as shown below

[Bug Mc-10975] – When the moon Bunny appears, it is not possible to click on the pop-up and select text elements

The reason is that all events are captured by your moon rabbit

How do I avoid events being captured by target elements? Use CSS to set the pointer-events attribute to None. If set to None, the target element will never be the target of mouse events

Add a line of style pointer-events: None to the CSS of the #cursor element; Can be

Moon cake trajectory implementation

With the above implementation of the yutu pointer experience to achieve the moon cake trajectory is very easy

Each moon cake element is drawn with a div, and the initial style sheet for the moon cake is added to the page first

const orbitSize = '40px'
addStyles(document.body, `
    .orbit{
        background-image:url(https://img.cdn.sugarat.top/mdImg/MTYzMTMyNDMwODg2Nw==631324308867);
        width:${orbitSize};
        height:${orbitSize};
        background-size:${orbitSize} ${orbitSize};
        position:fixed;
        display:none;
        cursor:none;
        pointer-events: none;
    }
`)
Copy the code

The moon cake track limit is set to 5 moon cakes. Create a simple loop

const ybCounts = 5
const domList = []
for (let i = 0; i < ybCounts; i++) {
    const d = h()
    d.classList.add('orbit')
    domList.push(d)
    document.body.append(d)
}
Copy the code

Create an array to store the last five positions of the pointer, and a temporary variable to store subsequent track point information

const posList = []
let now = 0
Copy the code

Write the refreshOrbit method to update the trace:

  • And since the trajectory has a scaling effect, the pie gets smaller as you go back, so it goes throughmaxScaleDetermine the maximum magnification
  • According to the number of trajectory points, determine eachThe moon cakeThe final scaling factor
  • According to the stored pointer location information, update the mooncake location one by one
function refreshOrbit(x, y) {
    // Refresh the position
    const maxScale = 1.5
    const minScale = maxScale / domList.length
    posList.forEach(({ x, y }, idx) = > {
        const dom = domList[idx]
        dom.style.display = 'block'
        dom.style.left = `${x}px`
        dom.style.top = `${y}px`
        dom.style.transform = `scale(${(idx + 1) * minScale}) translate(10%,10%)`
        if (dom.timer) {
            clearTimeout(dom.timer)
        }
        dom.timer = setTimeout(() = > {
            dom.style.display = 'none'
        }, 50 * (idx + 1))})const nowTime = Date.now()
    // Store one at a time
    if (now + 50 > nowTime) {
        return
    }
    now = nowTime
    posList.push({
        x, y
    })
    // Only a limited number can be stored
    if (posList.length === ybCounts+1) {
        posList.shift()
    }
}
Copy the code

Call the update trace method in time rollback

window.addEventListener('mousemove'.function (e) {
    const { clientX, clientY } = e
    / /... Omit other code
    // Update the mooncake trajectory
    refreshOrbit(clientX, clientY)
})
Copy the code

Mobile terminal support

This is easy, just listen for the TouchMove event

window.addEventListener('touchmove'.function (e) {
    const { clientX, clientY } = e.changedTouches[0]
    refreshCursorPos(clientX, clientY)
    
    // Update the mooncake trajectory
    refreshOrbit(clientX, clientY)
})
Copy the code

The last

In the future, we will share the script of setting pointer style with a general JS SDK. How to change pointer style

Everyone has a better plan to exchange a wave in the comment section