Search code examples
javascriptdraggablebounce

Bouncing draggable ball


I have this draggable ball when you drag and release, it is going the oposite way. How can I make this bounce from the walls? Also if it is supposed to travel 600px and it hit a wall after 200px, it should go for 400px more. http://jsfiddle.net/bepcmsgo/5/

let circle = document.getElementById("circle");
let pos1 = 0;
let pos2 = 0;
let pos3 = 0;
let pos4 = 0;
let mouseDown = false;
let currentCircleTop = 0;
let currentCircleLeft = 0;

circle.addEventListener("mousedown", function(e){
  mouseDown = true;
  pos3 = e.pageX;
  pos4 = e.pageY;
  currentCircleTop = circle.offsetTop;
  currentCircleLeft = circle.offsetLeft;
  this.style.transition = "0.3s all";
})

document.addEventListener("mousemove", function(e){
    if(mouseDown){
        pos1 = pos3 - e.pageX;
        pos2 = pos4 - e.pageY;
        pos3 = e.pageX;
        pos4 = e.pageY;
        circle.style.top = circle.offsetTop - pos2 + "px";
        circle.style.left = circle.offsetLeft - pos1 + "px";
  }
})
document.addEventListener("mouseup", function(){
    if(mouseDown){
        mouseDown = false;
        circle.style.transition = "0.8s all";  
        circle.style.top = currentCircleTop + ((currentCircleTop - circle.offsetTop) * 20) + "px";
        circle.style.left = currentCircleLeft + ((currentCircleLeft - circle.offsetLeft) * 20) + "px";
    }
})

Solution

  • I forked your JSFiddle and added a simple bouncing mechanic: jsfiddle.net/SydLambert/hnkL392x/2

    let circle = document.getElementById("circle");
    circle.x=300;
    circle.y=150;
    circle.direction=0; //in radians
    circle.velocity=0;
    circle.friction=0.05; //0 to 1
    
    let container=document.getElementById("circleContainer");
    container.width=parseInt(container.style.width.slice(0,-2)); //Gets width and height as usable integers
    container.height=parseInt(container.style.height.slice(0,-2));
    
    let mouse={
        x:0,
        y:0,
        down:false,
    };
    
    let dragDisplayMultiplier=0.2; //How much the circle moves while a drag is in progress.
    let velocityDampener=5; //How much the velocity is reduced immediately after the circle is released
    
    
    function displayCircle(){ //Sets the position of the circle
        circle.style.top=circle.y+"px";
        circle.style.left=circle.x+"px"
    }
    
    function step(){ //Uses trig to work out the next positon of the circle
        return {
            x:circle.x+circle.velocity*Math.cos(circle.direction),
            y:circle.y+circle.velocity*Math.sin(circle.direction),
        }
    }
    
    function tick(){ //Physics function
        circle.velocity*=1-circle.friction; //Decrease the circle's velocity with friction
    
        let newLocation=step(); //Determine the next location after the circle has travelled
    
        //If the next location of the circle is outside the container, the direction is changed.
        //Angle of incidence equals angle of reflection.
        if(newLocation.x<0 || newLocation.x+20>container.width)
            circle.direction=Math.PI-(circle.direction);
        if(newLocation.y<0 || newLocation.y+20>container.height)
            circle.direction*=-1;
    
        //The next location is now inside the container, so the circle's position can be updated
        newLocation=step();
        circle.x=newLocation.x;
        circle.y=newLocation.y;
    
        //Displays the circle's new position to the user
        displayCircle();
    
        //If the circle still has reasonable velocity, the simulation is continued after waiting 16ms
        if(circle.velocity>1){
            setTimeout(tick,16);
        }
    }
    
    circle.addEventListener("mousedown", function(e){
        mouse.down=true;
    });
    
    document.addEventListener("mousemove", function(e){
        mouse.x=e.pageX;
        mouse.y=e.pageY;
        if(mouse.down) //Offsets the ball whilst the drag is in progress using CSS translation
            circle.style.transform=`translate(${((circle.x+mouse.x)/2-circle.x)*dragDisplayMultiplier}px, ${((circle.y+mouse.y)/2-circle.y)*dragDisplayMultiplier}px)`;
    });
    
    document.addEventListener("mouseup", function(){
        if(mouse.down){
            mouse.down = false;
            circle.style.transform="translate(0px, 0px)"; //Resets the CSS translation from the "mousemove" event
            circle.velocity=Math.sqrt((circle.x-mouse.x+10)**2 + (circle.y-mouse.y+10)**2); //Sets the velocity to the distance bewteen the circle and mouse pointer
            circle.velocity/=velocityDampener; //Reduces the velocity slightly for ease of use
            circle.direction=Math.atan2((circle.y-mouse.y+10),(circle.x-mouse.x+10)); //Uses atan2 to find the angle between the circle and the mouse pointer
            setTimeout(tick,16); //Starts the physics simulation
        }
    });
    
    displayCircle(); //Sets the initial circle top and left to match x and y
    

    I changed the ball movement logic slightly, here are some pointers that explain my approach:

    • Using CSS animations is perfectly fine for simple movements, but anything more dynamic and complex is best left to JS to save yourself a lot of CSS code.
    • In this example, all movements are handled frame-by-frame in JS, with angle & friction calculations occurring before each frame is rendered, instead of calculating the final position and animating the path there. Each tick calculates the immediate next position (bouncing if necessary) and then displays to the user. The delay between ticks is 16ms to make it ~62.5FPS.
    • I moved your position variables to named properties in the circle class to make it easier to read than having pos1, pos2, pos3... all as global variables.
    • The math is quite googleable, I used pythag to get the distance to the mouse pointer and trig to get the angle to the pointer & move the circle. The bounce angle assumes that the angle of incidence equals angle of reflection. The bounce also assumes perfectly elastic collision, although you could decrease velocity on a bounce if you wanted.
    • I changed the slight movement before release to use CSS translation instead of actually moving the element.
    • One final note, I would suggest looking into HTML5 <canvas> if you want to do more graphical stuff, it was built for stuff like this.