HTML preprocessors can make writing HTML more powerful or convenient. For instance, Markdown is designed to be easier to write and read for text documents and you could write a loop in Pug.
In CodePen, whatever you write in the HTML editor is what goes within the <body>
tags in a basic HTML5 template. So you don't have access to higher-up elements like the <html>
tag. If you want to add classes there that can affect the whole document, this is the place to do it.
In CodePen, whatever you write in the HTML editor is what goes within the <body>
tags in a basic HTML5 template. If you need things in the <head>
of the document, put that code here.
The resource you are linking to is using the 'http' protocol, which may not work when the browser is using https.
CSS preprocessors help make authoring CSS easier. All of them offer things like variables and mixins to provide convenient abstractions.
It's a common practice to apply CSS to a page that styles elements such that they are consistent across all browsers. We offer two of the most popular choices: normalize.css and a reset. Or, choose Neither and nothing will be applied.
To get the best cross-browser support, it is a common practice to apply vendor prefixes to CSS properties and values that require them to work. For instance -webkit-
or -moz-
.
We offer two popular choices: Autoprefixer (which processes your CSS server-side) and -prefix-free (which applies prefixes via a script, client-side).
Any URLs added here will be added as <link>
s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.
You can apply CSS to your Pen from any stylesheet on the web. Just put a URL to it here and we'll apply it, in the order you have them, before the CSS in the Pen itself.
You can also link to another Pen here (use the .css
URL Extension) and we'll pull the CSS from that Pen and include it. If it's using a matching preprocessor, use the appropriate URL Extension and we'll combine the code before preprocessing, so you can use the linked Pen as a true dependency.
JavaScript preprocessors can help make authoring JavaScript easier and more convenient.
Babel includes JSX processing.
Any URL's added here will be added as <script>
s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.
You can apply a script from anywhere on the web to your Pen. Just put a URL to it here and we'll add it, in the order you have them, before the JavaScript in the Pen itself.
If the script you link to has the file extension of a preprocessor, we'll attempt to process it before applying.
You can also link to another Pen here, and we'll pull the JavaScript from that Pen and include it. If it's using a matching preprocessor, we'll combine the code before preprocessing, so you can use the linked Pen as a true dependency.
Search for and use JavaScript packages from npm here. By selecting a package, an import
statement will be added to the top of the JavaScript editor for this package.
Using packages here is powered by esm.sh, which makes packages from npm not only available on a CDN, but prepares them for native JavaScript ESM usage.
All packages are different, so refer to their docs for how they work.
If you're using React / ReactDOM, make sure to turn on Babel for the JSX processing.
If active, Pens will autosave every 30 seconds after being saved once.
If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.
If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.
Visit your global Editor Settings.
<canvas id='gridwormCanvas' width='1350' height='620' style='background-color: white;' ></canvas>
/*
*Animates connected nodes about a grid
*-------------------------------------
*@date: 19th April, 2020
*@email: redutron@protonmail.com
*/
//set up the gridworm
class GridWorm
{
constructor(point,interval,pointsList,screenWidth,screenHeight)
{
this.radius = 2;
this.xCoord = point.x;
this.yCoord = point.y;
this.interval= interval;
this.color = this.getColor(1,true);//get random color object
this.mainColor = this.color.color;//color of the head and body of the girdworm
this.mainColorIndex = this.color.index;
this.nColor = this.getColor(1,true);//get another random color object
this.arrowHeadColor = this.nColor.color;//color of the arrrow points at the head of the gridworm
this.arrowHeadColorIndex = this.nColor.index;
this.pointsList = pointsList;
this.screenWidth = screenWidth;
this.screenHeight= screenHeight;
this.speed = 5;//the magnitude of the velocity
this.velocity= this.getVelocity();
this.junctionMemory = [{point:point,velocity:this.velocity}];//memory of each junction visited(helps to construct the worm)
//the maximum number of junctions a gridworm can keep in memory(this determines how long the gridworm will be)
this.junctionMemoryLength = 6;
}
getColor(opacity,isRandom = true,index = 0)
{
if(opacity < 0 || opacity > 1 || opacity === null || isNaN(opacity))//if opacity is incorrect
{
opacity = 1;
}
var colors =
[
`rgba(0,0,0,${opacity})`,`rgba(192,192,192,${opacity})`/*silver*/,`rgba(128,128,128,${opacity})`/*gray*/,`rgba(128,0,0,${opacity})`/*maroon*/,
`rgba(255,0,0,${opacity})`/*red*/,`rgba(0,255,0,${opacity})`/*lime*/,`rgba(0,0,255,${opacity})`/*blue*/,`rgba(255,0,255,${opacity})`/*fuchsia*/,
`rgba(128,128,0,${opacity})`/*olive*/,`rgba(0,128,0,${opacity})`/*green*/,`rgba(128,0,128,${opacity})`/*purple*/,
`rgba(0,128,128,${opacity})`/*teal*/,`rgba(0,0,128,${opacity})`/*navy*/,`rgba(138,57,0,${opacity})`/*brown*/, `rgba(205,133,63,${opacity})`,
`rgba(244,164,96,${opacity})`,`rgba(139,105,30,${opacity})`,`rgba(165,42,42,${opacity})`,`rgba(178,34,34,${opacity})`,
`rgba(220,20,60,${opacity})`,`rgba(255,140,0,${opacity})`,`rgba(255,165,0,${opacity})`,`rgba(255,215,0,${opacity})`,`rgba(184,134,11,${opacity})`,
`rgba(218,165,32,${opacity})`,`rgba(218,165,32,${opacity})`,`rgba(238,232,170,${opacity})`,`rgba(189,183,107,${opacity})`,`rgba(240,230,140,${opacity})`,
`rgba(0,100,0,${opacity})`, `rgba(34,139,34,${opacity})`,`rgba(32,178,170,${opacity})`,`rgba(47,79,79,${opacity})`,
`rgba(0,139,139,${opacity})`,`rgba(95,158,160,${opacity})`,`rgba(70,130,180,${opacity})`,`rgba(25,25,112,${opacity})`,
`rgba(0,0,128,${opacity})`,`rgba(0,0,139,${opacity})`,`rgba(72,61,139,${opacity})`,`rgba(75,0,130,${opacity})`,`rgba(139,0,139,${opacity})`,
`rgba(0,0,0,${opacity})`,`rgba(105,105,105,${opacity})`, `rgba(169,169,169,${opacity})`
];
if(isRandom)
{
let index = Math.floor(this.getRandomNumber(0,colors.length-1));
let color = colors[index];
return {color:color,index:index};
}
else//if specific
{
if(index >=0 && index < colors.length)
{
return colors[index];
}
return colors[0];
}
}
getVelocity()
{
let x,y;
//flip a coin to decide if gridworm moves vertically or horizontally
if( Math.random() > 0.5)//if gridworm moves vertically
{
x = 0;//no horizontal movement
y = Math.random() > 0.5? -this.speed: this.speed;//flip a coin to decide if gridworm moves upwards or downwards
}
else//if gridworm moves horizontally
{
x = Math.random() > 0.5? -this.speed: this.speed;//flip a coin to decide if gridworm moves left or right
y = 0;//no vertical movement
}
return {x:x, y:y};
}
/**
* Returns a random number between min (inclusive) and max (exclusive)
* @param {number} min The lesser of the two numbers.
* @param {number} max The greater of the two numbers.
* @return {number} A random number between min (inclusive) and max (exclusive)
*/
getRandomNumber(min, max)
{
return Math.random() * (max - min) + min;
}
drawCircle(x,y,circleradius,ctx,colorIndex)
{
for(let i = 0; i < 3; i++)
{
let color = '';
let radius = 0;
switch(i)//create three circles with same center
{
case 0:
radius =circleradius;//smallest circle
color = this.getColor(1,false,colorIndex);
break;
case 1:
radius =circleradius * 2;//bigger circle
color = this.getColor(0.5,false,colorIndex);
break;
case 2:
radius =circleradius * 6;//biggest circle
color = this.getColor(0.2,false,colorIndex);
break;
}
//draw the node
ctx.beginPath();
ctx.arc(x,y,radius,0,2*Math.PI);
ctx.fillStyle = color;
ctx.fill();
ctx.strokeStyle = color;
ctx.stroke();
}
}
drawArrowHead(x,y,circleradius,ctx,colorIndex)
{
let points = [];
if(this.velocity.x === 0)//if gridworm is moving vertically
{
if(this.velocity.y > 0)//if gridworm is moving down
{
points.push({x:x+this.interval/3,y:y});//point to the right
points.push({x:x-this.interval/3,y:y});//point to the left
points.push({x:x,y:y+this.interval/3});//point below
}
else//if gridworm is moving up
{
points.push({x:x+this.interval/3,y:y});//point to the right
points.push({x:x-this.interval/3,y:y});//point to the left
points.push({x:x,y:y-this.interval/3});//point above
}
}
else//if gridworm is moving horizontally
{
if(this.velocity.x > 0)//if gridworm is moving right
{
points.push({x:x+this.interval/3,y:y});//point to the right
points.push({x:x,y:y-this.interval/3});//point above
points.push({x:x,y:y+this.interval/3});//point below
}
else//if gridworm is moving left
{
points.push({x:x-this.interval/3,y:y});//point to the left
points.push({x:x,y:y-this.interval/3});//point above
points.push({x:x,y:y+this.interval/3});//point below
}
}
//draw a circle about the points that make the arrow head
for(let i = 0; i < points.length;i++)
{
let point = points[i];
this.drawCircle(point.x,point.y,circleradius/2,ctx,colorIndex);
}
this.drawTriangle(points[0],points[1],points[2],ctx);//draw the arrow head
}
drawTriangle(point1,point2,point3,ctx)
{
ctx.beginPath();
ctx.moveTo(point1.x, point1.y);
ctx.lineTo(point2.x, point2.y);
ctx.lineTo(point3.x, point3.y);
ctx.fillStyle = 'rgba(0,0,0,0.1)';//transparent black
ctx.fill();
}
draw(ctx)
{
//draw the head of the gridworm
this.drawCircle(this.xCoord,this.yCoord,this.radius/2,ctx,this.mainColorIndex);
this.drawArrowHead(this.xCoord,this.yCoord,this.radius/2,ctx,this.arrowHeadColorIndex);
//draw circles and squares at every visited junctions in the gridworm's memory(not RAM)
for(let i = 0; i < this.junctionMemory.length; i++)
{
let junction = this.junctionMemory[this.junctionMemory.length -(i+1)];
//draw a circle at each junction point
this.drawCircle(junction.point.x, junction.point.y,this.radius/2,ctx,this.mainColorIndex);
//draw painted squares at every junction point
ctx.fillStyle = this.getColor(0.1,false,this.mainColorIndex);
ctx.fillRect(junction.point.x,junction.point.y,this.interval,this.interval);
}
//draw the line connecting head to body
ctx.strokeStyle = 'black';
ctx.lineWidth = this.radius;
ctx.beginPath();
ctx.moveTo(this.xCoord,this.yCoord);
//draw a line to link all the visited junctions in the gridworm's memory(not RAM)
for(let i = 0; i < this.junctionMemory.length; i++)
{ //starting from the most recent to the least recent(LIFO)[NB: more like a stack data structure]
let junction = this.junctionMemory[this.junctionMemory.length -(i+1)];
ctx.lineTo(junction.point.x, junction.point.y);
}
ctx.stroke();
ctx.closePath();
}
update(deltaTime)
{
this.junctionMemoryLength = this.junctionMemoryLength < 1? 1: this.junctionMemoryLength;
//keep the gridworm moving in its current direction
this.xCoord += this.velocity.x;//if gridworm is going left or right, keep it going
this.yCoord += this.velocity.y;//if gridworm is going up or down, keep it going
if(this.xCoord <= this.interval)//if gridworm reaches the leftmost point
{
this.xCoord = this.interval;
this.velocity.x = -this.velocity.x;//move right
this.xCoord += this.velocity.x * 3;//nudge it a bit away from the edge
}
if(this.xCoord >= this.screenWidth - this.interval)//if gridworm reaches the rightmost point
{
this.xCoord = this.junctionMemory[this.junctionMemory.length-1].point.x;
this.velocity.x = -this.velocity.x;//move left
this.xCoord += this.velocity.x * 3;//nudge it a bit away from the edge
}
if(this.yCoord <= this.interval)//if gridworm reaches the topmost most point
{
this.yCoord = this.interval;
this.velocity.y = -this.velocity.y; //move down
this.yCoord += this.velocity.y * 3;//nudge it a bit away from the edge
}
if(this.yCoord >= this.screenHeight - this.interval)//if gridworm reaches the lowest point)
{
this.yCoord = this.junctionMemory[this.junctionMemory.length-1].point.y;
this.velocity.y = -this.velocity.y;//move up
this.yCoord += this.velocity.y * 4;//nudge it a bit away from the edge
}
let currentCoord = {x:this.xCoord,y:this.yCoord};
let latestJunction = this.getJunctionReached(currentCoord);
if(latestJunction !== currentCoord)
{
let originalVelocity = this.velocity;
let newVelocity = this.getVelocity();//flip a coin to decide to move up and down or right and left
if(originalVelocity.y === 0 )//if gridworm is moving horizontally
{
this.velocity = newVelocity;
if(newVelocity.y === 0 && newVelocity.x === -originalVelocity.x )//if it continues the horizontal movement in the opposite direction
{
//don't add the new junction to the memory queue
}
else
{
let memory = {point:latestJunction,velocity:this.velocity};
if(!this.isInMemory(memory))
{
this.junctionMemory.push(memory);//add new memory to the queue
}
//this.junctionMemory.push({point:latestJunction,velocity:this.velocity});//add new memory to the queue
}
//nudge it a bit away from the junction
this.xCoord += this.velocity.x * 3; //not complete yet. Don't make it too much or too little.
}
else //if gridworm is moving vertically
{
this.velocity = newVelocity;
if(newVelocity.x === 0 && newVelocity.y === -originalVelocity.y )//if it continues the verticalal movement in the opposite direction
{
//don't add the new junction to the memory queue
}
else
{
let memory = {point:latestJunction,velocity:this.velocity};
if(!this.isInMemory(memory))
{
this.junctionMemory.push(memory);//add new memory to the queue
}
}
//nudge it a bit away from the junction
this.yCoord += this.velocity.y * 3; //not complete yet. Don't make it too much or too little.
}
}
if(this.junctionMemory.length > this.junctionMemoryLength)//if memory is too long
{
this.junctionMemory.shift();//remove the first memory
}
}
isInMemory(memory)//check if a junction is in memory
{
this.junctionMemory.some(function(mem)
{
if(mem.point === memory.point)
{
return true;//junction is in memory
}
return mem.point === memory.point;
});
return false;//junction is NOT in memory
}
getJunctionReached(currentCoord)
{
for(let i = 0; i < this.pointsList.length; i++)
{
let point = this.pointsList[i];
//if point(junction) is too far away, ignore it
if(Math.abs(currentCoord.x - point.x) > (2 * this.interval) || Math.abs(currentCoord.y - point.y) > (2 *this.interval) )
{
continue;
}
let distance = this.getDistance(currentCoord,point);
if(distance <= (this.radius))//if gridworm head is close enough to a junction
{
return point;
}
}
return currentCoord;
}
getDistance(p1,p2)//the distance between two points, p1 and p2
{
let dx = p1.x - p2.x;
let dy = p1.y - p2.y;
let distance = Math.sqrt(dx*dx + dy*dy);
return distance;
}
/**
* Let node correspond to window resizing.
* @param {number} screenHeight The height of the screen.
* @param {number} screenWidth The width of the screen.
* @param {number} dy The percentage change in browser window height
* @param {number} dx The percentage change in browser window width .
*/
refreshScreenSize(screenHeight,screenWidth,dx,dy,points)
{
}
}
//sets up and controls all points and gridworms on the canvas
class Painter
{
constructor(screenWidth,screenHeight)
{
this.screenWidth = screenWidth;
this.screenHeight = screenHeight;
this.interval = 40;//interval from one point to the next
this.points = this.createPoints(); //coordinates of the vertices of all squares when the canvas is partitioned
this.gridWorms = this.createGridWorms();
this.color = this.getRandomColor(0.1);
document.addEventListener('click',(event)=>//when user clicks on the canvas
{
this.points = this.createPoints();
this.gridWorms = this.createGridWorms();//spawn new gridworms
this.color = this.getRandomColor(0.1);
});
}
createGridWorms()
{
let gridworms = [],
numOfGridWorms = 30;
for(var i = 0; i < numOfGridWorms; i++)
{
let point = this.points[Math.floor(this.getRandomNumber(0,this.points.length-1))];//randomly select a point
gridworms.push(new GridWorm(point,this.interval,this.points,this.screenWidth,this.screenHeight));
}
return gridworms;
}
createPoints()//divide the canvas into squares
{
let points = [],
interval = this.interval;//interval from one point to the next
for(var y = interval; y < this.screenHeight; y+=interval)//get all points in the grid, starting from the top to the bottom
{
if(y+interval > this.screenHeight)//if the next point is beyond the right edge of the canvas
{
continue; //skip
}
for(var x = interval; x < this.screenWidth; x+=interval)//all the while, getting all the horizontal points at each level
{
if(x+interval > this.screenWidth)//if the next point is beyond the bottom edge of the canvas
{
continue; //skip
}
points.push({x:x,y:y});
}
}
return points;
}
getRandomColor(opacity)
{
var colors = [
`rgba(255,0,0, ${opacity})`,//red
`rgba(255, 242,0, ${opacity})`,//yellow,
`rgba(0,0,255, ${opacity})`,//blue
`rgba(255,255,0, ${opacity})`,//yellow
`rgba(0,255,255, ${opacity})`,//cyan
`rgba(255,0,255, ${opacity})`,//magenta/fuchsia
`rgba(192,192,192, ${opacity})`,//silver
`rgba(128,128,128, ${opacity})`,//gray
`rgba(128,0,0, ${opacity})`,//maroon
`rgba(128,128,0, ${opacity})`,//olive
`rgba(0,128,0, ${opacity})`,//green
`rgba(128,0,128, ${opacity})`,//purple
`rgba(0,128,128, ${opacity})`,//teal
`rgba(0,0,128, ${opacity})`,//navy
`rgba(0, 255, 0, ${opacity})`,//green
`rgba(77, 0, 255, ${opacity})`,//blue
`rgba(255, 0, 140, ${opacity})`,//purple
`rgba(0,255,0, ${opacity})`//lime
];
return colors[parseInt(this.getRandomNumber(0, colors.length))];
}
/**
* Returns a random number between min (inclusive) and max (exclusive)
* @param {number} min The lesser of the two numbers.
* @param {number} max The greater of the two numbers.
* @return {number} A random number between min (inclusive) and max (exclusive)
*/
getRandomNumber(min, max)
{
return Math.random() * (max - min) + min;
}
/**
* Let canvas respond to window resizing.
* @param {number} screenHeight The height of the screen.
* @param {number} screenWidth The width of the screen.
*/
refreshScreenSize(screenHeight,screenWidth)
{
if(this.screenHeight !== screenHeight || this.screenWidth !== screenWidth)//if the screen size has changed
{
this.screenHeight = screenHeight;
this.screenWidth = screenWidth;
this.points = this.createPoints(); //coordinates of the vertices of all squares when the canvas is partitioned
this.gridWorms = this.createGridWorms();
}
}
update(deltaTime)
{
this.gridWorms.forEach(function(gridworm)
{
gridworm.update(deltaTime);
});
}
draw(ctx)
{
/*
for(var i = 0; i < this.points.length; i++)
{
let point = this.points[i];
ctx.fillStyle = Math.random() > 0.5? this.color:'white';//creates a disco effect
ctx.fillRect(point.x,point.y,this.interval,this.interval);
}
*/
this.gridWorms.forEach(function(gridworm)
{
gridworm.draw(ctx);
});
}
}
//set everything up
function getBrowserWindowSize()
{
let win = window,
doc = document,
offset = 20,//
docElem = doc.documentElement,
body = doc.getElementsByTagName('body')[0],
browserWindowWidth = win.innerWidth || docElem.clientWidth || body.clientWidth,
browserWindowHeight = win.innerHeight|| docElem.clientHeight|| body.clientHeight;
return {x:browserWindowWidth-offset,y:browserWindowHeight-offset};
}
let browserWindowSize = getBrowserWindowSize(),
c = document.getElementById("gridwormCanvas"),
ctx = c.getContext("2d");
//set size of canvas
c.width = browserWindowSize.x;
c.height = browserWindowSize.y;
let SCREEN_WIDTH = browserWindowSize.x,
SCREEN_HEIGHT= browserWindowSize.y,
painter = new Painter(SCREEN_WIDTH,SCREEN_HEIGHT),
lastTime = 100,
windowSize;
function onWindowResize()//called every time the window gets resized.
{
windowSize = getBrowserWindowSize();
c.width = windowSize.x;
c.height = windowSize.y;
SCREEN_WIDTH = windowSize.x;
SCREEN_HEIGHT = windowSize.y;
}
window.addEventListener('resize',onWindowResize);
function updateCanvas()
{
ctx.clearRect(0,0,SCREEN_WIDTH,SCREEN_HEIGHT);
ctx.fillStyle = 'white';
ctx.fillRect(0,0,SCREEN_WIDTH,SCREEN_HEIGHT);
}
function doAnimationLoop(timestamp)
{
updateCanvas();
painter.refreshScreenSize(SCREEN_HEIGHT,SCREEN_WIDTH);//let canvas respond to window resizing
let deltaTime = timestamp - lastTime;
lastTime = timestamp;
painter.update(deltaTime);
painter.draw(ctx);
requestAnimationFrame(doAnimationLoop);
}
requestAnimationFrame(doAnimationLoop);
Also see: Tab Triggers