Dynamically Map an Area of an Image using JavaScript/jQuery Tutorial
In this tutorial, you will learn how to Dynamically Map an Area of an Image using JavaScript/jQuery. This will help you to understand also on how to put a detection link on a specific part or area of the image. With the use of HTML Canvas, we can dynamically locate the specific position and size of the area we want to detect on the image.
Below, I have provided a simple source code of a simple web application. The application displays an image and allows users to add details on a specific area of an image. The user can simply click the "Map Area" button to start to draw in the image that will identify the area on the image, Next, the user will click the "Save Mapped Area" button to open a form modal where he/she will put the area details. Then, after saving the details the page will be reloaded and the mapped area will become a clickable area. When the area has been clicked, a modal will show on the window that displays the area details. Users can also update the area details and delete the mapped area.
Getting Started
The source code I created below, I used Bootstrap version 5 as the CSS Framework and jQuery Library. Please download the said framework and library in or display and run the application properly. The following are the links of the framework and library website to download.
After downloading the framework and library, compile them inside a single directory that will serve as your source code folder. Also, add an image file in the directory because the application does not have an upload feature.
Creating the Interface
The below code is a HTML Script that contains the user interfaces codes of the application. This holds the image, canvas, map, area, and modal HTML tag. Open a text editor and copy the script below and paste in into a new html file. Next, change the image source according to the image file location you desired to use. Save the file as index.html.
- <!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">
- <link rel="stylesheet" href="./css/bootstrap.min.css">
- <!-- Custom CSS -->
- <style>
- :root {
- --bs-success-rgb: 71, 222, 152 !important;
- }
- html,
- body {
- height: 100%;
- width: 100%;
- font-family: Apple Chancery, cursive;
- }
- #fp-canvas-container {
- height: 65vh;
- width: calc(100%);
- position: relative;
- }
- .fp-img,
- .fp-canvas,
- .fp-canvas-2 {
- position: absolute;
- width: calc(100%);
- height: calc(100%);
- top: 0;
- left: 0;
- z-index: 1;
- }
- #fp-map {
- position: absolute;
- width: calc(100%);
- height: calc(100%);
- top: 0;
- left: 0;
- z-index: 1;
- }
- .fp-canvas {
- z-index: 2;
- background: #0000000d;
- cursor: crosshair;
- }
- #fp-map {
- z-index: 2;
- }
- area {
- position: absolute;
- }
- area:hover {
- background: #cbcbcb0f;
- }
- #save,
- #cancel {
- display: none;
- }
- </style>
- <!-- End of Custom Css -->
- </head>
- <body class="bg-light">
- <nav class="navbar navbar-expand-lg navbar-dark bg-primary bg-gradient" id="topNavBar">
- <div class="container">
- <a class="navbar-brand" href="https://sourcecodester.com">
- Sourcecodester
- </a>
- </div>
- </nav>
- <div class="container py-3" id="page-container">
- <hr>
- <div class="content">
- <div class="row my-2">
- <!-- Action Buttons -->
- <div class="col-md-12">
- </div>
- <!-- End ofAction Buttons -->
- </div>
- <!-- Image/Canvas/Map Container -->
- <div id="fp-canvas-container">
- <map name="fp-map" id="fp-map">
- </map>
- <img src="./devices.jpg" alt="Floor Plan" class='fp-img' id="fp-img" usemap="#fp-map">
- <canvas class="fp-canvas d-none" id="fp-canvas"></canvas>
- </div>
- </div>
- <!-- End of Image/Canvas/Map Container -->
- </div>
- </div>
- <!-- Mapped Area Add/Edit Details Form Modal -->
- <div class="modal fade" id="form_modal" role='dialog' data-bs-backdrop="static" data-bs-keyboard="true">
- <div class="modal-dialog modal-md modal-dialog-centered" role="document">
- <div class="modal-content">
- <div class="modal-header">
- </div>
- <div class="modal-body">
- <form action="" id="mapped-form">
- <input type="hidden" name="id" value="">
- <input type="hidden" name="coord_perc" value="">
- <div class="form-group">
- </div>
- </form>
- </div>
- <div class="modal-footer py-1">
- </div>
- </div>
- </div>
- </div>
- <!--End of Mapped Area Add/Edit Details Form Modal -->
- <!--Mapped Area View Details Modal -->
- <div class="modal fade" id="view_modal" role='dialog' data-bs-backdrop="static" data-bs-keyboard="true">
- <div class="modal-dialog modal-md modal-dialog-centered" role="document">
- <div class="modal-content">
- <div class="modal-header">
- </div>
- <div class="modal-body">
- </div>
- <div class="modal-footer py-1">
- </div>
- </div>
- </div>
- </div>
- <!--End of Mapped Area View Details Modal -->
- </body>
- </html>
Creating the Main Script
The below script is a JavaScript codes that contains the drawing, map area creation, buttons actions, and modal triggers codes. Save the following file as script.js.
- // Canvas Position Variables
- var cposX = 0,
- cposY = 0;
- // Mouse Down Position Variables
- var posX = 0,
- posY = 0;
- // Mouse Moving Position Variables
- var nposX = 0,
- nposY = 0;
- // Coordinate Position in Percentage Variables
- var px1_perc = 0,
- py1_perc = 0,
- px2_perc = 0,
- py2_perc = 0;
- // Canvas Variable
- var ctx;
- // Variable for checking if the mouse has started to draw or not
- var isDraw = false;
- // Auto Increment sequence for the Store Data Identification [id]
- var AI_seq = $.parseJSON(localStorage.getItem('seq')) || 0;
- // Stored Data [Mapped Area Details]
- var stored = $.parseJSON(localStorage.getItem('mapped')) || {};
- // Function that creates the Area tag and appends to the Map Tag
- function mapped_area() {
- if (Object.keys(stored).length > 0) {
- // Empty Map Tag First
- $('#fp-map').html('')
- // Looping Data
- Object.keys(stored).map(k => {
- // Loop Current Data
- var data = stored[k]
- // Creating New Area Tag
- var area = $("<area shape='rect'>")
- area.attr('href', "javascript:void(0)")
- // Coordinate Percentage
- var perc = data.coord_perc
- perc = perc.replace(" ", '')
- perc = perc.split(",")
- // Configuring Area Position, Height, and Width
- var x = $('#fp-img').width() * perc[0];
- var y = $('#fp-img').height() * perc[1];
- var width = Math.abs(($('#fp-img').width() * Math.abs(perc[2])) - x);
- var height = Math.abs(($('#fp-img').height() * Math.abs(perc[3])) - y);
- if (($('#fp-img').width() * perc[2]) - x < 0)
- x = x - width
- if (($('#fp-img').height() * perc[3]) - y < 0)
- y = y - height
- area.attr('coords', x + ", " + y + ", " + width + ", " + height)
- area.addClass('fw-bolder text-muted')
- area.css({
- 'height': height + 'px',
- 'width': width + 'px',
- 'top': y + 'px',
- 'left': x + 'px',
- })
- $('#fp-map').append(area)
- // Action to make if the Area Tag has been clicked
- area.click(function() {
- $('#view_modal').find('#edit-area,#delete-area').attr('data-id', data.id)
- data.description = data.description.replace(/\n/gi, "<br>")
- $('#view_modal').find('.modal-body').html(data.description)
- $('#view_modal').modal('show')
- })
- })
- }
- }
- $(function() {
- cposX = $('#fp-canvas')[0].getBoundingClientRect().x
- cposY = $('#fp-canvas')[0].getBoundingClientRect().y
- ctx = $('#fp-canvas')[0].getContext('2d');
- // Creates the Map Area on the Image
- mapped_area()
- // Re-initialize Map Area Creation when the window has been resized
- $(window).on('resize', function() {
- mapped_area()
- })
- // Event Listener when the mouse is clicked on the canvas area
- $('.fp-canvas').on('mousedown', function(e) {
- px1_perc = (e.clientX - cposX) / $('#fp-canvas').width()
- py1_perc = (e.clientY - cposY) / $('#fp-canvas').height()
- posX = $('#fp-canvas')[0].width * ((e.clientX - cposX) / $('#fp-canvas').width());
- posY = $('#fp-canvas')[0].height * ((e.clientY - cposY) / $('#fp-canvas').height());
- isDraw = true
- })
- // Event Listener when the mouse is moving on the canvas area. For drawing the rectangular Area
- $('.fp-canvas').on('mousemove', function(e) {
- if (isDraw == false)
- return false;
- nposX = $('#fp-canvas')[0].width * ((e.clientX - cposX) / $('#fp-canvas').width());
- nposY = $('#fp-canvas')[0].height * ((e.clientY - cposY) / $('#fp-canvas').height());
- var height = nposY - posY;
- var width = nposX - posX;
- ctx.clearRect(0, 0, $('.fp-canvas')[0].width, $('.fp-canvas')[0].height);
- ctx.beginPath();
- ctx.lineWidth = ".3";
- ctx.strokeStyle = "pink";
- ctx.rect(posX, posY, width, height);
- ctx.stroke();
- })
- // Event Listener when the mouse is up on the canvas area. End of Drawing
- $('.fp-canvas').on('mouseup', function(e) {
- px2_perc = (e.clientX - cposX) / $('#fp-canvas').width()
- py2_perc = (e.clientY - cposY) / $('#fp-canvas').height()
- nposX = $('#fp-canvas')[0].width * ((e.clientX - cposX) / $('#fp-canvas').width());
- nposY = $('#fp-canvas')[0].height * ((e.clientY - cposY) / $('#fp-canvas').height());
- var height = nposY - posY;
- var width = nposX - posX;
- ctx.clearRect(0, 0, $('.fp-canvas')[0].width, $('.fp-canvas')[0].height);
- ctx.beginPath();
- ctx.lineWidth = ".3";
- ctx.strokeStyle = "pink";
- ctx.rect(posX, posY, width, height);
- ctx.stroke();
- isDraw = false
- })
- // Action when Map Are button is clicked
- $('#map_area').click(function() {
- $(this).hide('slow')
- $('#save,#cancel').show('slow')
- $('#fp-canvas').removeClass('d-none')
- cposX = $('#fp-canvas')[0].getBoundingClientRect().x
- cposY = $('#fp-canvas')[0].getBoundingClientRect().y
- })
- // Action when Map Are cancel is clicked
- $('#cancel').click(function() {
- $('#save,#cancel').hide('slow')
- $('#map_area').show('slow')
- $('#fp-canvas').addClass('d-none')
- })
- // Action when Map Are save is clicked
- $('#save').click(function() {
- var cP = px1_perc + ", " + py1_perc + ", " + px2_perc + ", " + py2_perc
- $('#form_modal').find('input[name="coord_perc"]').val(cP)
- $('#form_modal').modal('show')
- })
- // Saving the Mapped Area Details on local Storage
- $('#mapped-form').submit(function(e) {
- e.preventDefault();
- var data;
- var id = $(this).find('[name="id"]').val()
- var coord_perc = $(this).find('[name="coord_perc"]').val()
- var description = $(this).find('[name="description"]').val()
- if (id == '') {
- id = AI_seq + 1;
- localStorage.setItem('seq', id)
- }
- data = { id: id, description: description, coord_perc: coord_perc }
- stored[id] = data;
- localStorage.setItem('mapped', JSON.stringify(stored))
- alert("Mapped Area Successfully saved.")
- location.reload()
- })
- // Edit Mapped Area Details
- $('#edit-area').click(function() {
- $('.modal').modal('hide')
- id = $(this).attr('data-id')
- data = stored[id] || {}
- $('#mapped-form').find('[name="id"]').val(data.id)
- $('#mapped-form').find('[name="coord_perc"]').val(data.coord_perc)
- $('#mapped-form').find('[name="description"]').val(data.description)
- $('#form_modal').modal('show')
- })
- // Delete Mapped Area
- $('#delete-area').click(function() {
- $('.modal').modal('hide')
- id = $(this).attr('data-id')
- data = stored[id] || {}
- var _conf = confirm("Are you sure to delete the selected mapped area?")
- if (_conf === true) {
- if (!!stored[id])
- delete stored[id];
- }
- localStorage.setItem('mapped', JSON.stringify(stored))
- alert("Selected Mapped Area Successfully Deleted.")
- location.reload()
- })
- })
That's it. You can now try to run the application in your browser and see if it works as it was planned to. If you found any errors, kindly review the source code on your end. You can also download the working source code I created for this tutorial. The download button is located below this article.
DEMO VIDEO
That is the end of this tutorial. I hope you will find this tutorial useful for your future web application projects. Explore more on this website for more Tutorials and Free Source Codes.
Happy Coding :)
Add new comment
- 1201 views