Identifying selected shapes in CSS/Javascript by outlining them with small rectangles placed on each corner

Currently, I am working on a University Project that involves creating a selection function for drawn shapes such as rectangles, circles, lines, or triangles.

The challenge lies in figuring out the visualization of the selection when a shape is clicked. The goal is to have small rectangles displayed at the corners, similar to Microsoft Word's selection handles:

https://i.sstatic.net/NMLXe.png

I'm struggling with changing the borders or border-corners of the shapes to show these small rectangles. Can you provide guidance on how to achieve this effect?

Thank you for your assistance!

var draw_area = document.getElementById('draw_area');

function drawRect(){
  var object = draw_area.getContext('2d');
  object.beginPath();
  object.rect(50, 50, 100, 100);
  object.stroke();
};

function drawLine(){

  var object = draw_area.getContext('2d');
  object.beginPath();
  object.moveTo(230, 100);
  object.lineTo(330, 100);
  object.stroke();

};

function drawTriangle(){

  var object = draw_area.getContext('2d');
  object.beginPath();
  object.moveTo(420, 50);
  object.lineTo(470, 150);
  object.lineTo(370, 150);
  object.lineTo(420, 50);
  object.stroke();

};

function drawCircle(){

  var object = draw_area.getContext('2d');
  object.beginPath();
  object.arc(600, 100, 50, 0, 2 * Math.PI);
  object.stroke(); 

}

drawRect();
drawLine();
drawTriangle();
drawCircle();
#draw_area{
  background-color: lightgrey;
}
<div>
  <canvas id="draw_area" height="700", width="700"> </canvas>
</div>

Answer №1

I outlined the corners of a rectangle:

class DrawingTool
{
    constructor(drawArea)
  {
    this.drawArea = drawArea;
    this.canvasContext = drawArea.getContext('2d');
    this.shapes = [];
    
    drawArea.addEventListener('click', this.handleMouseClick.bind(this), false);
  }
  
  draw()
  {
    this.canvasContext.clearRect(0, 0, 700, 700);
    this.shapes.forEach((shape) => shape.draw());
  }
  
  drawRectangle(x, y, width, height)
  {
    this.shapes.push(new RectangleShape(this.canvasContext, x, y, width, height));
  }
    
  handleMouseClick(event)
  {
    var xCoord = event.pageX - this.drawArea.offsetLeft + this.drawArea.clientLeft;
    var yCoord = event.pageY - this.drawArea.offsetTop + this.drawArea.clientTop;

    this.shapes.forEach((shape) => 
    {
        if (yCoord > shape.y && yCoord < shape.y + shape.height && xCoord > shape.x && xCoord < shape.x + shape.width) 
        {
            shape.selected = !shape.selected;
        }
    });
    
    this.draw();
  }
}

class RectangleShape
{
    constructor(context, x, y, width, height)
  {
    this.context = context;
    this.x = x;
    this.y = y;
    this.width = width;
    this.height = height;
    this.cornerSize = 10;
    this.selected = false;
  }
  
  draw()
  {
    
    this.context.beginPath();
    this.context.rect(this.x, this.y, this.width, this.height);
    this.context.closePath();
    this.context.stroke();
    
    if(!this.selected)
        return;
    
    this.context.beginPath();

    this.context.fillStyle = "#FFFFFF";
    this.context.strokeStyle = "#000000";

        
    // Corner rectangles
    this.context.rect(
      this.x - this.cornerSize / 2,
      this.y - this.cornerSize / 2,
      this.cornerSize, 
      this.cornerSize
    );

    this.context.rect(
      this.x + this.width - this.cornerSize / 2,
      this.y - this.cornerSize / 2,
      this.cornerSize, 
      this.cornerSize
    );

    this.context.rect(
      this.x + this.width - this.cornerSize / 2,
      this.y + this.height - this.cornerSize / 2,
      this.cornerSize, 
      this.cornerSize
    );

    this.context.rect(
      this.x - this.cornerSize / 2,
      this.y + this.height - this.cornerSize / 2,
      this.cornerSize, 
      this.cornerSize
    );

    this.context.closePath();
    this.context.fill();
    this.context.stroke();
  }
}

var drawArea = document.getElementById('draw_area');
let drawingTool = new DrawingTool(drawArea);

drawingTool.drawRectangle(50, 50, 100, 100);
drawingTool.drawRectangle(200, 50, 100, 100);
drawingTool.draw();
#draw_area{
  background-color: lightgrey;
}
<div>
  <canvas id="draw_area" height="700", width="700"> </canvas>
</div>

Examining the drawRectangle function:

function drawRectangle(x, y, width, height){
    const cornerSize = 10;
  var object = drawArea.getContext('2d');
  object.beginPath();
  object.rect(x, y, width, height);
  object.closePath();

  object.stroke();
  object.beginPath();
  
  object.fillStyle = "#FFFFFF";
  object.strokeStyle = "#000000";

  
  // Corner rectangles
  object.rect(
    x - cornerSize / 2,
    y - cornerSize / 2,
    cornerSize, 
    cornerSize
  );
  
  object.rect(
    x + width - cornerSize / 2,
    y - cornerSize / 2,
    cornerSize, 
    cornerSize
  );
  
  object.rect(
    x + width - cornerSize / 2,
    y + height - cornerSize / 2,
    cornerSize, 
    cornerSize
  );
  
  object.rect(
    x - cornerSize / 2,
    y + height - cornerSize / 2,
    cornerSize, 
    cornerSize
  );
  
  object.closePath();
  object.fill();
  object.stroke();

};

These lines are initially for outlining the main rectangle:

object.beginPath();
object.rect(x, y, width, height);
object.closePath();    
object.stroke();

Calling closePath here is crucial so that the canvas distinguishes the main rectangle from the corner rectangles.

Subsequently, calculations are made to accurately position and draw the corner rectangles:

// top-left corner
object.rect(
    x - cornerSize / 2,
    y - cornerSize / 2,
    cornerSize, 
    cornerSize
);

The use of closePath and fill at the end ensures adherence to coloring guidelines mentioned previously:

object.fillStyle = "#FFFFFF";
object.strokeStyle = "#000000";

Note that manual handling is required when using pure canvas without frameworks like Fabric.js; even with libraries, manual calculation of small rectangle positions per shape may still be necessary.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

performing a query on two tables with sequelize

There must be a more efficient way to accomplish this task with fewer lines of code. I am not very experienced with databases and I am new to sequelize and node. The user id is passed as a parameter and I need to check if there is a corresponding user in ...

Why does the parent URL become the origin for an AJAX request coming from an iframe?

I am facing an issue with a website where I need to load an iframe from a different subdomain. The main website is hosted on portal.domain.com, and the iframe is on iframe.domain.com. To make requests to iframe.domain.com from portal.domain.com, I decided ...

Optimal techniques for leveraging CSS within Mui and Reactjs

Just starting out with mui, I'm currently working on styling CSS for mui components like so <Typography variant="h5" sx={{ fontWeight: "bold", color: "#1a759f", display: "flex", ...

Testing the local transmission of form data via Javascript: A Step-by-Step guide

Currently studying how to send forms using JavaScript by manually creating an XMLHttpRequest. Towards the end of the provided example, there's a note: Note: If you want to send data to a third party website, keep in mind that this use of XMLHttpRequ ...

Learn how to dynamically chain where conditions in Firebase without prior knowledge of how many conditions will be added

Currently, I am working on a project using Angular and firebase. My goal is to develop a function that can take two arguments - a string and an object, then return an Observable containing filtered data based on the key-value pairs in the object for a spe ...

Is there a way to confirm whether or not two files are identical?

Is there a reliable method to determine if two files are identical? I've been using a solution that involves downloading the first part of each file, converting the data to base64, and then comparing them. However, I've encountered an issue wher ...

Transforming the MVC model attribute into an HTML attribute

Is there a way to include model attributes in the HTML input tag? Code [Required] [StringLength(20)] public string Username { get; set; } [Required] [StringLength(20)] [DataType(DataType.Password)] public string Password { get; set; } Expected Output: ...

When evaluating code with eval, properties of undefined cannot be set, but the process works seamlessly without

Currently, I am attempting to utilize the eval() function to dynamically update a variable that must be accessed by path in the format myArray[0][0[1][0].... Strangely enough, when I try this approach, I encounter the following error: Uncaught TypeError: ...

Transport the unique identifier of the table row

After retrieving the list of followers from Instagram and storing it in an array in a session, I am displaying the data in a tabular format. <table class="tablesorter" cellspacing="0"> <thead> <tr> <th>&l ...

What is the best method for extracting one specific data point from a database table?

Currently, I am faced with a challenge in fetching data from one database table to another. Despite successfully utilizing SQL's JOIN feature for this task, I still struggle with isolating a single result from the database and showcasing it on the res ...

If the URL hash matches the ID of a specific div, then take action on the div's child element

Imagine I have the following HTML structure: <div class=".blog-item-holder"> <div id="AAAA"><div class="inner">AAAA</div></div> <div id="BBBB"><div class="inner">BBBB</div></div> <div id="CCCC">& ...

The entire space on an HTML page should be occupied with content from the header to the footer

I am currently implementing jQuery Mobile on my project. The page I have created consists of a fixed header and footer with only two buttons. Due to the fixed footer, half of the page is in silver color (data-theme=c) while the bottom half is gray. My goa ...

Is it possible to adjust the height of sibling divs within a sequence to match the tallest div?

Do you have a set of inline div containers with hardcoded widths but varying content heights? Is there a way to ensure that all divs maintain the same height without risking content overflowing from its parent container? I've experimented with inher ...

Can you provide guidance on how to pass the selected value from a select option to an onchange function using Vue.js methods?

I'm facing an issue with passing the selected option value id from a select option to an onchange function in my methods. My goal is to store the selected value in the v-model "choosed" every time the user changes the select option, and then pass that ...

Locate the XPath for text that is within a div tag and adjacent to a span tag

I've searched, but couldn't find an exact match, so my question is about a low HTML code: <div class="column1"> <div> <div class="name"> Dynamic Name <span class="id" title="ID">Dynamic ID</span> </div> </d ...

How can I implement pagination using jQuery?

I'm looking to incorporate jQuery pagination in my CodeIgniter project. After doing some research on the CodeIgniter forum and CodeIgniter AJAX Pagination Example/Guideline, I came across suggestions to check out a solution on TOHIN's blog. Howe ...

CSS Flexbox - Choose all elements that appear at the start of a new line (Wrap)

Is there a CSS selector that can prevent content insertion on new lines when using the wrap feature in CSS-Flexbox? I am utilizing CSS-Flexbox for a layout with the wrap option, but I do not know the number of lines beforehand. I need a solution where the ...

choose exclusively the text within the elementor navigation menu

I've been tinkering with this issue for a few hours now. I have a vertical Elementor navigation menu and I'd like to add a hover effect to it. So far, I can only select the entire column and apply the effect to that, not just the length of the t ...

Bring in a JavaScript file to a Read-Evaluate-Print-Loop (

Currently experimenting on Windows 10 TP build 9926 with nodejs, I am attempting to import a JavaScript file into an active session of nodejs that is operating in the Windows command prompt. This will allow me to call functions from the imported script dir ...

Formatting a pair of elements side by side in MUI Select

Currently, I am tackling the challenge of organizing elements in MUI Select v4. My goal is to display these elements in two columns rather than just one column within the dropdown. Despite attempting to override certain styles within MUI, I have not been ...