I made a square surface in which all 4 sides are portals to the opposite side just like the 2d representation of a torus surface using only html, css and vanilla javascript.
If you would like a video representation of the same here is a link to the video:
the html
we start by doing a basic setup of the html and adding a html5 canvas tag
<!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>TORUS</title>
<link rel="stylesheet" href="style.css">
<script src="script.js" defer></script>
</head>
<body>
<canvas id="canvas1"></canvas>
</body>
</html>
The css
We also setup the css by setting the margins, paddings and box sizing.
*{
margin:0;
padding:0;
box-sizing:border-box;
font-family: 'Courier New', Courier, monospace;
}
We also set up the body to ensure the canvas element is centered
body{
background-color: #c4fdfd4b;
width: 100vw;
height: 100vh;
display:flex;
justify-content: center;
align-items: center;
}
We complete the css setup by setting the canvas element
#canvas1{
background-color: #f7debfa6;
position: relative;
height:500px;
width:700px;
top:0;
left:0;
border:solid black 2px;
}
The js
canvas sizing
We begin by sizing the canvas by setting its size the same to the height and width written in the css
const canvas = document.getElementById('canvas1')
const ctx = canvas.getContext('2d')
canvas.width=700
canvas.height=500
Controls
We set the variables to control the motion of the ball
let dx = 2
let dy = 2
document.addEventListener('keydown',function(e){
switch(e.code){
case 'ArrowUp':
dy=-2
break
case 'ArrowLeft':
dx=-2
break
case 'ArrowDown':
dy=2
break
case 'ArrowRight':
dx=2
break
}
})
ball class
The class to make the ball
class Ball{
constructor(){
this.radius=50
this.x=canvas.width/2
this.y=canvas.height/2
this.xpos1=this.x
this.xpos2=this.x
this.ypos1=this.y
this.ypos2=this.y
}
update(){
this.x+=dx
this.y+=dy
this.xpos1+=dx
this.xpos2+=dx
this.ypos1+=dy
this.ypos2+=dy
///////////////////////////////////////
if(this.x>=canvas.width+this.radius && dx==2){
this.x=this.x-canvas.width
}else if(this.x<=-this.radius && dx==-2){
this.x=this.x+canvas.width
}
///////////////////////////////////////
if(this.y>=canvas.height+this.radius && dy==2){
this.y=this.y-canvas.height
}else if(this.y<=-this.radius && dy==-2){
this.y=this.y+canvas.height
}
}
draw(){
ctx.lineWidth=2
ctx.strokeStyle='black'
ctx.fillStyle='green'
ctx.beginPath()
ctx.arc(this.x,this.y,this.radius,0,Math.PI*2)
ctx.stroke()
ctx.closePath()
ctx.fill()
}
}
animate function
This is the function that moves the ball
let ball1 = new Ball()
function animateMotion(){
ctx.clearRect(0,0,canvas.width,canvas.height)
ball1.draw()
ball1.update()
requestAnimationFrame(animateMotion)
}
animateMotion()
duplicate method
We add a duplicate method to the ball class to duplicate the ball as it goes through the portal
class Ball{
constructor(){
this.radius=50
this.x=canvas.width/2
this.y=canvas.height/2
this.xpos1=this.x
this.xpos2=this.x
this.ypos1=this.y
this.ypos2=this.y
}
update(){
this.x+=dx
this.y+=dy
this.xpos1+=dx
this.xpos2+=dx
this.ypos1+=dy
this.ypos2+=dy
///////////////////////////////////////
if(this.x>=canvas.width+this.radius && dx==2){
this.x=this.x-canvas.width
}else if(this.x<=-this.radius && dx==-2){
this.x=this.x+canvas.width
}
///////////////////////////////////////
if(this.y>=canvas.height+this.radius && dy==2){
this.y=this.y-canvas.height
}else if(this.y<=-this.radius && dy==-2){
this.y=this.y+canvas.height
}
}
draw(){
ctx.lineWidth=2
ctx.strokeStyle='black'
ctx.fillStyle='green'
ctx.beginPath()
ctx.arc(this.x,this.y,this.radius,0,Math.PI*2)
ctx.stroke()
ctx.closePath()
ctx.fill()
}
duplicate(xcor,ycor){
ctx.lineWidth=2
ctx.strokeStyle='black'
ctx.fillStyle='green'
ctx.beginPath()
ctx.arc(xcor,ycor,this.radius,0,Math.PI*2)
ctx.stroke()
ctx.closePath()
ctx.fill()
}
}
clone tracking
This is to know when the ball is in contact with the portal so it can make the clone. It is added to the update method in the ball class
class Ball{
constructor(){
this.radius=50
this.x=canvas.width/2
this.y=canvas.height/2
this.xpos1=this.x
this.xpos2=this.x
this.ypos1=this.y
this.ypos2=this.y
}
update(){
this.x+=dx
this.y+=dy
this.xpos1+=dx
this.xpos2+=dx
this.ypos1+=dy
this.ypos2+=dy
///////////////////////////////////////
if(this.x>=canvas.width+this.radius && dx==2){
this.x=this.x-canvas.width
}else if(this.x<=-this.radius && dx==-2){
this.x=this.x+canvas.width
}
if(this.x>=canvas.width-this.radius){
this.xpos1=this.x-canvas.width
}else if(this.x<=this.radius){
this.xpos2=this.x+canvas.width
}else{
this.xpos1=this.x
this.xpos2=this.x
}
///////////////////////////////////////
if(this.y>=canvas.height+this.radius && dy==2){
this.y=this.y-canvas.height
}else if(this.y<=-this.radius && dy==-2){
this.y=this.y+canvas.height
}
if(this.y>=canvas.height-this.radius){
this.ypos1=this.y-canvas.height
}else if(this.y<=this.radius){
this.ypos2=this.y+canvas.height
}else{
this.ypos1=this.y
this.ypos2=this.y
}
}
draw(){
ctx.lineWidth=2
ctx.strokeStyle='black'
ctx.fillStyle='green'
ctx.beginPath()
ctx.arc(this.x,this.y,this.radius,0,Math.PI*2)
ctx.stroke()
ctx.closePath()
ctx.fill()
}
duplicate(xcor,ycor){
ctx.lineWidth=2
ctx.strokeStyle='black'
ctx.fillStyle='green'
ctx.beginPath()
ctx.arc(xcor,ycor,this.radius,0,Math.PI*2)
ctx.stroke()
ctx.closePath()
ctx.fill()
}
}
clone conditions
This are the conditions that have to be met for the clone to be created. This is added to the update method.
class Ball{
constructor(){
this.radius=50
this.x=canvas.width/2
this.y=canvas.height/2
this.xpos1=this.x
this.xpos2=this.x
this.ypos1=this.y
this.ypos2=this.y
}
update(){
this.x+=dx
this.y+=dy
this.xpos1+=dx
this.xpos2+=dx
this.ypos1+=dy
this.ypos2+=dy
///////////////////////////////////////
if(this.x>=canvas.width+this.radius && dx==2){
this.x=this.x-canvas.width
}else if(this.x<=-this.radius && dx==-2){
this.x=this.x+canvas.width
}
if(this.x>=canvas.width-this.radius){
this.xpos1=this.x-canvas.width
}else if(this.x<=this.radius){
this.xpos2=this.x+canvas.width
}else{
this.xpos1=this.x
this.xpos2=this.x
}
///////////////////////////////////////
if(this.y>=canvas.height+this.radius && dy==2){
this.y=this.y-canvas.height
}else if(this.y<=-this.radius && dy==-2){
this.y=this.y+canvas.height
}
if(this.y>=canvas.height-this.radius){
this.ypos1=this.y-canvas.height
}else if(this.y<=this.radius){
this.ypos2=this.y+canvas.height
}else{
this.ypos1=this.y
this.ypos2=this.y
}
///////////////////////////////////////////////////////////////////
if(this.x>=canvas.width-this.radius || this.x<=this.radius || this.y>=canvas.height-this.radius || this.y<=this.radius ){
this.duplicate(this.xpos1,this.ypos1)
this.duplicate(this.xpos2,this.ypos2)
this.duplicate(this.xpos1,this.ypos2)
this.duplicate(this.xpos2,this.ypos1)
}
}
draw(){
ctx.lineWidth=2
ctx.strokeStyle='black'
ctx.fillStyle='green'
ctx.beginPath()
ctx.arc(this.x,this.y,this.radius,0,Math.PI*2)
ctx.stroke()
ctx.closePath()
ctx.fill()
}
duplicate(xcor,ycor){
ctx.lineWidth=2
ctx.strokeStyle='black'
ctx.fillStyle='green'
ctx.beginPath()
ctx.arc(xcor,ycor,this.radius,0,Math.PI*2)
ctx.stroke()
ctx.closePath()
ctx.fill()
}
}
Top comments (0)