Preface: After learning JavaScript, you can use JavaScript to achieve some interesting effects. This article explains how to use JavaScript purely to implement an electronic spider on a web page.
Before we start learning how to write a web spider, let's take a look at what this electronic spider looks like:
You can see that it will move with our mouse, so how do you achieve this effect? Let's start explaining.
HTML Code
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dreaming</title>
<!-- External JavaScript files -->
<script src=".test.js"></script>
<style>
/* Remove default padding and margins from body */
body {
margin: 0px;
padding: 0px;
position: fixed;
/* Set the background color of webpage to black */
background: rgb(0, 0, 0);
}
</style>
</head>
<body>
<!-- Create a canvas for drawing -->
<canvas id="canvas"></canvas>
</body>
</html>
As you can see our HTML code is very simple, let's start working on it!
Before you start writing JavaScript code, create a plan:
Overall process
When the page loads, the canvas element and drawing context are initialized.
Define tentacle objects . Each tentacle consists of multiple segments.
Listen to mouse movement events and update the mouse position in real time.
The tentacles are drawn through an animation loop, and they change dynamically according to the position of the mouse, creating a smooth animation effect.
The general process is the above steps, but I believe that you may not understand the above process before you have completed the writing of this code, but it doesn't matter anyways, so let's start writing our web spider:
Preface: In order to help you better understand the logic of the code, I have added comments to each code. I hope that you can understand the code bit by bit with the help of the comments:
JavaScript Code
// Define requestAnimFrame function
window.requestAnimFrame = function () {
// Check if the browser supports requestAnimFrame function
return (
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
// If all these options are unavailable, use setTimeout to call the callback function
function (callback) {
window.setTimeout(callback)
}
)
}
// Initialization function to get canvas element and return related information
function init(elemid) {
// Get canvas element
let canvas = document.getElementById(elemid)
// Get 2d drawing context, note that 'd' is lowercase
c = canvas.getContext('2d')
// Set canvas width to window inner width and height to window inner height
w = (canvas.width = window.innerWidth)
h = (canvas.height = window.innerHeight)
// Set fill style to semi-transparent black
c.fillStyle = "rgba(30,30,30,1)"
// Fill the entire canvas with the fill style
c.fillRect(0, 0, w, h)
// Return drawing context and canvas element
return { c: c, canvas: canvas }
}
// Execute function when page is fully loaded
window.onload = function () {
// Get drawing context and canvas element
let c = init("canvas").c,
canvas = init("canvas").canvas,
// Set canvas width to window inner width and height to window inner height
w = (canvas.width = window.innerWidth),
h = (canvas.height = window.innerHeight),
// Initialize mouse object
mouse = { x: false, y: false },
last_mouse = {}
// Function to calculate distance between two points
function dist(p1x, p1y, p2x, p2y) {
return Math.sqrt(Math.pow(p2x - p1x, 2) + Math.pow(p2y - p1y, 2))
}
// Define segment class
class segment {
// Constructor to initialize segment object
constructor(parent, l, a, first) {
// If it's the first tentacle segment, position is the tentacle top position
// Otherwise, position is the nextPos coordinates of the previous segment object
this.first = first
if (first) {
this.pos = {
x: parent.x,
y: parent.y,
}
} else {
this.pos = {
x: parent.nextPos.x,
y: parent.nextPos.y,
}
}
// Set segment length and angle
this.l = l
this.ang = a
// Calculate coordinates for the next segment
this.nextPos = {
x: this.pos.x + this.l * Math.cos(this.ang),
y: this.pos.y + this.l * Math.sin(this.ang),
}
}
// Method to update segment position
update(t) {
// Calculate angle between segment and target point
this.ang = Math.atan2(t.y - this.pos.y, t.x - this.pos.x)
// Update position coordinates based on target point and angle
this.pos.x = t.x + this.l * Math.cos(this.ang - Math.PI)
this.pos.y = t.y + this.l * Math.sin(this.ang - Math.PI)
// Update nextPos coordinates based on new position coordinates
this.nextPos.x = this.pos.x + this.l * Math.cos(this.ang)
this.nextPos.y = this.pos.y + this.l * Math.sin(this.ang)
}
// Method to return segment to initial position
fallback(t) {
// Set position coordinates to target point coordinates
this.pos.x = t.x
this.pos.y = t.y
this.nextPos.x = this.pos.x + this.l * Math.cos(this.ang)
this.nextPos.y = this.pos.y + this.l * Math.sin(this.ang)
}
show() {
c.lineTo(this.nextPos.x, this.nextPos.y)
}
}
// Define tentacle class
class tentacle {
// Constructor to initialize tentacle object
constructor(x, y, l, n, a) {
// Set tentacle top position coordinates
this.x = x
this.y = y
// Set tentacle length
this.l = l
// Set number of tentacle segments
this.n = n
// Initialize tentacle target point object
this.t = {}
// Set random movement parameter for tentacle
this.rand = Math.random()
// Create first segment of the tentacle
this.segments = [new segment(this, this.l / this.n, 0, true)]
// Create other segments
for (let i = 1; i < this.n; i++) {
this.segments.push(
new segment(this.segments[i - 1], this.l / this.n, 0, false)
)
}
}
// Method to move tentacle to target point
move(last_target, target) {
// Calculate angle between tentacle top and target point
this.angle = Math.atan2(target.y - this.y, target.x - this.x)
// Calculate tentacle distance parameter
this.dt = dist(last_target.x, last_target.y, target.x, target.y)
// Calculate tentacle target point coordinates
this.t = {
x: target.x - 0.8 * this.dt * Math.cos(this.angle),
y: target.y - 0.8 * this.dt * Math.sin(this.angle)
}
// If target point is calculated, update position coordinates of last segment object
// Otherwise, update position coordinates of last segment object to target point coordinates
if (this.t.x) {
this.segments[this.n - 1].update(this.t)
} else {
this.segments[this.n - 1].update(target)
}
// Iterate through all segment objects, update their position coordinates
for (let i = this.n - 2; i >= 0; i--) {
this.segments[i].update(this.segments[i + 1].pos)
}
if (
dist(this.x, this.y, target.x, target.y) <=
this.l + dist(last_target.x, last_target.y, target.x, target.y)
) {
this.segments[0].fallback({ x: this.x, y: this.y })
for (let i = 1; i < this.n; i++) {
this.segments[i].fallback(this.segments[i - 1].nextPos)
}
}
}
show(target) {
// If distance between tentacle and target point is less than tentacle length, draw tentacle
if (dist(this.x, this.y, target.x, target.y) <= this.l) {
// Set global composite operation to "lighter"
c.globalCompositeOperation = "lighter"
// Begin new path
c.beginPath()
// Start drawing line from tentacle starting position
c.moveTo(this.x, this.y)
// Iterate through all segment objects and use their show method to draw lines
for (let i = 0; i < this.n; i++) {
this.segments[i].show()
}
// Set line style
c.strokeStyle = "hsl(" + (this.rand * 60 + 180) +
",100%," + (this.rand * 60 + 25) + "%)"
// Set line width
c.lineWidth = this.rand * 2
// Set line cap style
c.lineCap = "round"
// Set line join style
c.lineJoin = "round"
// Draw line
c.stroke()
// Set global composite operation to "source-over"
c.globalCompositeOperation = "source-over"
}
}
// Method to draw tentacle's circular head
show2(target) {
// Begin new path
c.beginPath()
// If distance between tentacle and target point is less than tentacle length, draw white circle
// Otherwise draw cyan circle
if (dist(this.x, this.y, target.x, target.y) <= this.l) {
c.arc(this.x, this.y, 2 * this.rand + 1, 0, 2 * Math.PI)
c.fillStyle = "white"
} else {
c.arc(this.x, this.y, this.rand * 2, 0, 2 * Math.PI)
c.fillStyle = "darkcyan"
}
// Fill circle
c.fill()
}
}
// Initialize variables
let maxl = 400, // Maximum tentacle length
minl = 50, // Minimum tentacle length
n = 30, // Number of tentacle segments
numt = 600, // Number of tentacles
tent = [], // Array of tentacles
clicked = false, // Whether mouse is pressed
target = { x: 0, y: 0 }, // Tentacle target point
last_target = {}, // Previous tentacle target point
t = 0, // Current time
q = 10; // Step length for each tentacle movement
// Create tentacle objects
for (let i = 0; i < numt; i++) {
tent.push(
new tentacle(
Math.random() * w, // Tentacle x-coordinate
Math.random() * h, // Tentacle y-coordinate
Math.random() * (maxl - minl) + minl, // Tentacle length
n, // Number of tentacle segments
Math.random() * 2 * Math.PI, // Tentacle angle
)
)
}
// Method to draw image
function draw() {
// If mouse moves, calculate deviation between tentacle target point and current point
if (mouse.x) {
target.errx = mouse.x - target.x
target.erry = mouse.y - target.y
} else {
// Otherwise, calculate x-coordinate of tentacle target point
target.errx =
w / 2 +
((h / 2 - q) * Math.sqrt(2) * Math.cos(t)) /
(Math.pow(Math.sin(t), 2) + 1) -
target.x;
target.erry =
h / 2 +
((h / 2 - q) * Math.sqrt(2) * Math.cos(t) * Math.sin(t)) /
(Math.pow(Math.sin(t), 2) + 1) -
target.y;
}
// Update tentacle target point coordinates
target.x += target.errx / 10
target.y += target.erry / 10
// Update time
t += 0.01;
// Draw tentacle target point
c.beginPath();
c.arc(
target.x,
target.y,
dist(last_target.x, last_target.y, target.x, target.y) + 5,
0,
2 * Math.PI
);
c.fillStyle = "hsl(210,100%,80%)"
c.fill();
// Draw center points of all tentacles
for (i = 0; i < numt; i++) {
tent[i].move(last_target, target)
tent[i].show2(target)
}
// Draw all tentacles
for (i = 0; i < numt; i++) {
tent[i].show(target)
}
// Update previous tentacle target point coordinates
last_target.x = target.x
last_target.y = target.y
}
// Function to loop animation drawing
function loop() {
// Use requestAnimFrame function to loop
window.requestAnimFrame(loop)
// Clear canvas
c.clearRect(0, 0, w, h)
// Draw animation
draw()
}
// Listen for window resize event
window.addEventListener("resize", function () {
// Reset canvas size
w = canvas.width = window.innerWidth
h = canvas.height = window.innerHeight
// Loop animation drawing function
loop()
})
// Loop animation drawing function
loop()
// Use setInterval function to loop
setInterval(loop, 1000 / 60)
// Listen for mouse move event
canvas.addEventListener("mousemove", function (e) {
// Record previous mouse position
last_mouse.x = mouse.x
last_mouse.y = mouse.y
// Update current mouse position
mouse.x = e.pageX - this.offsetLeft
mouse.y = e.pageY - this.offsetTop
}, false)
// Listen for mouse leave event
canvas.addEventListener("mouseleave", function (e) {
// Set mouse to false
mouse.x = false
mouse.y = false
})
}
Here I roughly sort out the process of the above code:
Initialization phase
-
init
Function: When the page is loaded, the functioninit
is called to get thecanvas
element and set its width and height to the size of the window. The obtained 2D drawingcontext
is used for subsequent drawing. -
window.onload
: After the page is loaded, initialize canvas and setcontext
to the initial state of the mouse.
Definition of tentacle objects
-
segment
Class: This is a segment of a tentacle. Each segment has a starting point (pos
), length (l
), angle (ang
), and the position of the next segment is calculated by the angle ( nextPos). -
tentacle
Class: Represents a complete tentacle, consisting of severalsegment
. The starting point of the tentacle is at the center of the screen, and each tentacle contains multiple segments.
The main methods of tentacle
are:
move
: Update the position of each segment according to the mouse position.
show
: Draw the path of the tentacle.
Event monitoring
-
canvas.addEventListener('mousemove', ...)
: When the mouse moves, the mouse position is captured and stored in themouse
variable. Each mouse movement updates the coordinates ofmouse
andlast_mouse
for subsequent animations.
Animation loop
draw
Function : This is a recursive function used to create animation effects.
- First, it fills the canvas with a semi-transparent background in each frame, causing previously drawn content to gradually disappear, creating a smear effect.
- Then, iterate over all
tentacles
, calling theirmove
andshow
methods, updating their positions and drawing each frame. - Finally, use
requestAnimFrame(draw)
to make continuous recursivedraw
calls to form an animation loop.
Tentacle Behavior
- The movement of the tentacle
move
is implemented through the function. The last segment of the tentacle updates its position first, and then the other segments follow in sequence. - The drawing of the tentacles is done through the
show
function, which iterates through all segments and draws lines, and finally displays them on the screen.
In this way, you have completed the production of the electronic spider!!!
Finally, let's take a look at the final effect:
Top comments (0)