Q:

HTML5 Javascript: How to Make Sure a Drawn Object Cannot Leave the Canvas

I have the following assignment problem that I'm struggling to solve:

Write an application that uses a canvas element to create two circles (with different colors) in two different parts of the canvas. The circle should be able to be dragged within the canvas, and should stop when it gets to the edges. Put another way, the edge of the circle should not go past the edge of the canvas. Each circle should be dragged separately. When you release the mouse button, the circle should stop.

So far I've created the two circles, however, I'm having trouble with the part where I have to make it so that the two circles can be dragged but not off the canvas. I'm quite sure this is supposed to be done with an event listener but I can't figure out how to code this. Would greatly appreciate some assistance or insight.
What I have so far:

<!DOCTYPE html>
<html>
<head>
<script>
window.onload = draw;
function draw(){

var canvas = document.getElementById("circleCanvas");
var ctx = canvas.getContext("2d");
ctx.arc(100,75,50,0,2*Math.PI);
ctx.fillStyle = "green";
ctx.fill();
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.arc(100,200,50,0,2*Math.PI);
ctx.fillStyle = "blue";
ctx.fill();
ctx.stroke();
ctx.closePath();

}
</script>
</head>
<body>
<canvas id="circleCanvas" width="500" height="500"></canvas>
</body>
</html>

A:

Simple canvas drag and drop.

Create a mouse object and set the mouse positions and button state by listening to the mouse events.
Create an array of circles that describe the position and size of the each circle.
Create a function to draw a circle. In that function you check if the circle is out of bounds and move it if it is.
Create a position search function that checks each circle and how far it is from a point. If the circle is under the point (dist < circle.radius) then return a referance to that circle.
Create a animation loop that redraws the canvas every 1/60th of a second. In that check if the mouse is down. If it is and not dragging anything see if the mouse is over a circle. If it is select that circle for dragging. When the mouse is up drop the circle.

As it is an assignment we should not just give you the solution. But school is the only time in life when we are not allowed to ask for help, the time when we most need it. I assume that you are interested in learning and learning by example is in many situations the best.
If you have any questions please do ask

"use strict";
const canvas = document.createElement("canvas"); 
canvas.height = innerHeight;
canvas.width = innerWidth;
canvas.style.position = "absolute"; 
canvas.style.top = canvas.style.left = "0px";
const ctx = canvas.getContext("2d");
document.body.appendChild(canvas);
ctx.font = "48px arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
var w = canvas.width;
var h = canvas.height;
var renderUpdate  = true; // flags if there is a need to render
var globalTime = 0;
const mouse = {
    x:0,y:0,button:false
};
const circles = [{
        x : 100,
        y : 100,
        radius : 40,
        col : "red",
        lineWidth : 4,
        highlight : false,
    },{
        x : 200,
        y : 100,
        radius : 40,
        col : "green",
        lineWidth : 4,
        highlight : false,
    },        
];
var closestCircle; // holds result of function findClosestCircle2Point
const drag = {  // if dragging this holds the circle being dragged
    circle : null,
    offsetX : 0, // distance from mouse to circle center when drag started
    offsetY : 0,
}
const message = { // a message to inform of problems.
    time : 120,   // 60 ticks per second
    text: "Click drag circles",
}
// adds mouse events listeners
canvas.addEventListener("mousemove",mouseEvent)
canvas.addEventListener("mousedown",mouseEvent)
canvas.addEventListener("mouseup",mouseEvent)

// function to handle all mouse events
function mouseEvent(e){
    var m = mouse;
    var bounds = canvas.getBoundingClientRect();
    m.x = e.clientX - bounds.left; 
    m.y = e.clientY - bounds.top;     
    if(e.type === "mousedown"){
        m.button = true;
    }else if(e.type === "mouseup"){
        m.button = false;
    }
    renderUpdate = true; // flag that there could be a render change.
}



// this finds the closest circle under x,y if nothing under the point then retVal.circle = null
function findClosestCircle2Point(x, y, retVal){
    if(retVal === undefined){
        retVal = {};
    }
    var minDist = Infinity;
    var dist;
    var xx,yy;
    retVal.circle = null;
    for(var i = 0; i < circles.length; i ++){
        xx = x - circles[i].x;
        yy = y - circles[i].y;
        dist = Math.sqrt(xx*xx+yy*yy);
        if(dist < minDist && dist <= circles[i].radius){
            minDist = dist;
            retVal.circle = circles[i];
        }
    }
    return retVal;
}

// this draws a circle, adds highlight if needed and makes sure the circle does not go outside the canvas
function drawCircle(circle){
    var c = circle;
    var rad = c.radius + c.lineWidth / 2; // get radius plus half line width
    // keep circle inside canvas
    c.x = c.x - rad < 0 ? c.x = rad : c.x + rad >= w ? c.x = w-rad : c.x;
    c.y = c.y - rad < 0 ? c.y = rad : c.y + rad >= h ? c.y = h-rad : c.y;
    ctx.lineWidth = 4;
    if(c.highlight){  // highlight the circle if needed
        ctx.strokeStyle = "#0F0";
        ctx.globalAlpha = 0.5;
        ctx.beginPath();
        ctx.arc(c.x,c.y,c.radius + c.lineWidth,0,Math.PI * 2);
        ctx.stroke();
        c.highlight = false;
    }
    // draw the circle
    ctx.fillStyle = c.col;
    ctx.strokeStyle = c.col;
    ctx.globalAlpha = 0.5;
    ctx.beginPath();
    ctx.arc(c.x,c.y,c.radius,0,Math.PI * 2);
    ctx.fill();
    ctx.globalAlpha = 1;
    ctx.stroke();
}


// main update function
function update(time){
    globalTime = time;
    requestAnimationFrame(update); // get the next animation frame.
    if(!renderUpdate  ){  // don't render if there is no need
        return;
    }
    renderUpdate = false;


    ctx.clearRect(0,0,w,h);
    // when not dragging look for the closest circle under the mouse and highlight it
    if(drag.circle === null){
        closestCircle = findClosestCircle2Point(mouse.x,mouse.y,closestCircle); 
        if(closestCircle.circle !== null){
            closestCircle.circle.highlight = true;
        }
    }
    if(mouse.button){  // if the mouse is down start dragging if circle is under mouse
        if(drag.circle === null){
            if(closestCircle.circle !== null){
                drag.circle = closestCircle.circle;
                drag.offsetX = mouse.x - drag.circle.x;
                drag.offsetY = mouse.y - drag.circle.y;
            }else{
                mouse.button = false;
            }
        }else{
            drag.circle.x = mouse.x - drag.offsetX;
            drag.circle.y = mouse.y - drag.offsetY;
        }
    }else{   // drop circle 
        drag.circle = null;
    }
    for(var i = 0; i < circles.length; i ++){  // draw all circles
        drawCircle(circles[i]);
    }
    
    // display any messages if needed.
    if(message.time > 0){
        message.time -= 1;
        ctx.fillStyle = "black";
        ctx.fillText(message.text,w/2,h/2);
        renderUpdate = true; // while message is up need to render.
    }
    
}
requestAnimationFrame(update);  // start the whole thing going.

