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:
- When the mouse moves, the mouse is replaced with the Jade Rabbit
- 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 through
maxScale
Determine the maximum magnification - According to the number of trajectory points, determine each
The moon cake
The 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