Canvas image zoom and crop feature ensures fixed selection area across all platforms

After finding inspiration from a source I cannot name, I developed a plugin that allows for image cropping on both mobile and desktop devices.

The key features of my plugin include touch support, dynamic canvas sizing based on image dimensions, and the ability to move an image rather than just selecting an area. I initially considered purchasing a similar plugin but it did not meet my requirements when tested on different devices, prompting me to customize my own version.

You can view the working demo on JSFiddle.

This is the HTML structure:

<div class="container">
   <canvas id="panel" width="779" height="519"></canvas>
</div>

<input type="button" value="crop Selection" id="crop-image" />

<img id="croppedSelection" height="100px" width="100px" />

To initialize the plugin:

var Cropper = new CanvasCrop(document.getElementById('panel'), 'http://www.script-tutorials.com/demos/197/images/image.jpg');

$('#crop-image').on('click', function(){
   var src = Cropper.getBase64();
   $('#croppedSelection').attr('src', src);
});

I am now facing a challenge - how can I ensure that the selection area remains visible on the screen even when the parent image is scrolled, similar to the functionality seen here.

Any help or advice on this matter would be greatly appreciated.

As the source code is too extensive to include here, you can refer to the JSFiddle for a full demonstration. Thank you.

Edit

An updated version of the demo with fixed canvas height and width issues can be found on JSFiddle.

Answer №1

On a lazy afternoon, I found myself jotting down this code out of sheer boredom.

To implement this functionality, make sure to include the specified class in your code snippet. However, if you only require one viewer on the page, it's not necessary to use the class - everything can be encapsulated within a simple object. Should you need more viewers, consider the following:

function galleryViewer (html_viewer)
    {
    this.html_viewer=html_viewer;
    Object.defineProperty(html_viewer,"viewer_instance",{writeable:false,configureable:false,enumerable:false,value:this});
    this.selections = new Array ();
    html_viewer.addEventListener("mousedown",this.startScrolling);
    html_viewer.addEventListener("mouseup", this.endScrolling);
    html_viewer.addEventListener("scroll",this.setSelectionPosition);
    }

galleryViewer.prototype.startScrolling=function ()
    {
    var selections=this.viewer_instance.selections, selection;
    var l=selections.length;

    for (var i=0;i<l;i++)
        {
        selection=selections[i];
        selection.startLeft=selection.x;
        selection.startTop=selection.y;
        selection.viewer_startScrollLeft=this.scrollLeft;
        selection.viewer_startScrollTop=this.scrollTop;
        }
   }

galleryViewer.prototype.setSelectionPosition=function ()
  {
  var selections=this.viewer_instance.selections, selection;
  var l=selections.length;
  
  for (var i=0;i<l;i++)
    {
    selection=selections[i];
    selection.x=this.scrollLeft-selection.viewer_startScrollLeft+selection.startLeft;
    selection.y=this.scrollTop-selection.viewer_startScrollTop+selection.startTop;
    }

  this.viewer_instance.drawScene();
  }

 galleryViewer.prototype.endScrolling=function ()
   {
   var selections=this.viewer_instance.selections, selection;
   var l=selections.length;

   for (var i=0;i<l;i++)
     {
     selection=selections[i];
     selection.startLeft=null;
     selection.startTop=null;
     selection.viewer_startScrollLeft=null;
     selection.viewer_startScrollTop=null;
     }
   }

 galleryViewer.prototype.addSelection=function (selection)
    {
    selection.startLeft=null;
    selection.startTop=null;
    selection.viewer_startScrollLeft=null;
    selection.viewer_startScrollTop=null;
    selection.viewerIndex=this.selections.length;
    this.selections.push(selection);
    }

 galleryViewer.prototype.removeSelection=function (viewerIndex)
    {
    var selections=this.selections, l=selections.length, i;
    selection=selections[viewerIndex];
    delete selection.startLeft;
    delete selection.startTop;
    delete selection.viewer_startScrollLeft;
    delete selection.viewer_startScrollTop;
    delete selection.viewerIndex;

  for (i=viewerIndex+1;i<l;i++)
    selections[i].viewerIndex=i-1;  

  selections.splice(viewerIndex,1);
    }

var photoGalleryViewer= new galleryViewer(document.getElementById("panel").parentNode);

Insert the following line after line 27 theSelection = new Selection(64, 64, 64, 64);

Add

photoGalleryViewer.addSelection(theSelection);
photoGalleryViewer.drawScene=drawScene;

If you encounter any issues, feel free to reach out for assistance.

Answer №2

To properly implement image cropping, we need to discuss whether you prefer selecting a specific area within the canvas or resizing the entire imageCropper container before cropping.

// take a look here

Check out this JSFiddle link for more information.

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

getting data from JavaScript function by making an asynchronous request to RESTful API

My current challenge involves calling a JavaScript method that utilizes AJAX to access a REST service and then return the response to the original call. While this may seem straightforward, I have scoured Stack Overflow for answers but haven't found a ...

When using React and React Router v6, make sure to implement a 404 status code response for unmatched routes

When it comes to managing unmatched routes with React Router, I have a solid understanding: <Routes> {/* Public routes */} <Route exact path="/" element={<Home />} /> // Other routes... {/* Error routes */} ...

Stop iScroll from scrolling the listview filter

I have been working on integrating iScroll into my application using the javascript-jquery.mobile.iscroll plugin. My goal is to apply it to enable scrolling for a listview component on a specific page while keeping the filter fixed just below the header, a ...

Cursor hovers over button, positioned perfectly in the center

I am facing a challenge with implementing a hover function for a set of buttons on the screen. The goal is to display the mouse pointer at the center of the button upon hovering, rather than the actual position of the cursor being displayed. I have tried u ...

Is there an Angular Profile service offering getter and setter properties?

Can a singleton Angular service be created with getters and setters along with logic implementation? I was provided with the following code snippet and tasked with replicating it in an Angular service. Although it may seem straightforward, I'm finding ...

Repeated information displayed in modal pop-ups

HTML <a class="btn" data-popup-open="popup-1" href="#">More Details</a> <div class="popup" data-popup="popup-1"> <div class="popup-inner"> <h2>Unbelievable! Check this Out! (Popup #1)</h2> ...

Frontend experiencing issues with Laravel Echo Listener functionality

I have recently developed a new event: <?php namespace App\Events; use Illuminate\Broadcasting\Channel; use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Broadcasting\PresenceChannel; use Illuminate&bs ...

How can I retrieve the PHP response once a successful upload has occurred using DropzoneJS?

I am currently in the process of integrating Dropzone into my website. My goal is to capture the "success" event and extract specific information from the server response to add to a form on the same page as the DropZone once the upload is finished. The k ...

I'm having trouble sending a string to a WCF service using jQuery AJAX. What's preventing me from sending strings of numbers?

Something strange is happening with my web service when using jquery ajax - I can only pass strings containing numbers. This was never an issue before as I would always just pass IDs to my wcf service. But now that I'm trying something more complex, I ...

What is the most effective method for transmitting a zip file as a response in Azure functions with node.js?

With the Azure function app, my goal is to download images from various URLs and store them in a specific folder. I then need to zip these images and send the zip file back as a response. I have successfully achieved this by following these steps: Send ...

Angular - creating a specialized input field for a unique MatDialogConfig configuration file

I have a unique setup with a custom MaterialDialogConfig file dedicated to handling all of my material dialog components. Here's what the configuration file looks like: import { MatDialogConfig } from "@angular/material"; export class MaterialDialog ...

Seeking assistance with producing results

Is there someone who can provide an answer? What will be the output of the code snippet below when logged to the console and why? (function(){ var a = b = 3; })(); console.log("Is 'a' defined? " + (typeof a !== 'u ...

Displaying JSON data from the API on a webpage within a NodeJS weather application

Currently, I am in the process of developing a weather application using NodeJS. I have successfully retrieved JSON formatted data from the weather site's API. Nonetheless, I am perplexed about how to transmit this data to the application. Below is a ...

What are some ways to make source code more visually appealing on an HTML/JSP page as it is being

I've recently created a webpage with some Java source code, neatly organized within blocks. However, I'm looking to enhance its appearance so it truly looks like Java code. Take a look at my page's code here for reference: Are there any onl ...

Highcharts-ng allows us to create charts without using the traditional syntax such as $('#container').high

After setting up the chart's configuration in my controller, I am facing an issue. The HighCharts-ng (an angularJS directive for HighCharts) has a method that I want to implement: $scope.ohlcChartConfig = { options: {....... I ne ...

The ashx file is triggered with every server control postback in jqgrid

I have an asp .net webforms page with a jqgrid, as well as several other server controls. The jqgrid is populated using an ashx file which retrieves data from the database and binds it successfully. Challenge Whenever a postback occurs (such as from a dro ...

Tips for effectively running multiple XMLHttpRequests in a loop

While running XMLHttpRequest inside .map(), everything operates smoothly. list.map(function(file) { const xhr = new XMLHttpRequest(); xhr.onreadystatechange = function () {} xhr.onerror = function () {} xhr.upload.addEventListener(& ...

Enhance User Experience with a Responsive Website Dropdown Menu

Currently, I am focused on enhancing the responsiveness of my website and I realized that having a well-designed menu for mobile view is essential. To address this need, I added a button that only appears when the screen size is 480px or lower, which seems ...

Populate a bootstrap-select dropdown menu with an array of choices

After creating a table using datatables and adding an empty dropdown select on the footer with Bootstrap-select, here is the code snippet: <tfoot> <tr> <th><select class="selectpicker" multiple></select>< ...

Developing a countdown clock with php and asynchronous javascript (ajax)

Can anyone provide guidance on how to create a real-time database timer? I have a starting time in the database and I am using PHP, looking to incorporate JS or AJAX for optimal functionality. Here is a rough outline of my plan: BEGIN Request server ti ...