Trigger click events based on specific coordinates x and y in Fabric.js

While working on my project, I utilized three.js and fabric.js to manipulate textures on a 3D model. I managed to synchronize coordinates on the model with coordinates on the canvas in fabric js. However, I encountered an issue with simulating click events and fully managing the clicked objects. Fortunately, I came across helpful discussions on stackoverflow that address my problem. Thank you!

Programmatically select object in a Fabricjs canvas from coords

Raycast mouse clicks from Threejs model to the Fabricjs canvas used for the texture

Emit click events from one canvas to another

Check out the Codepen example here:

https://codepen.io/ricardcreagia/pen/EdEGod

Custom code is as follows:

        console.clear();
        console.log("starting scripts...");

        /**
         * Fabricjs
         * @type {fabric}
         */

        var canvas = new fabric.Canvas( "canvas" );
        canvas.backgroundColor = "#FFBE9F";

        var rectangle = new fabric.Rect( {
            top: 100,
            left: 100,
            fill: '#FF6E27',
            width: 100,
            height: 100,
            transparentCorners: false,
            centeredScaling: true,
            borderColor: 'black',
            cornerColor: 'black',
            corcerStrokeColor: 'black'
        } );

        canvas.add( rectangle );


        /**
         * Threejs
         */

        var containerHeight = "512";
        var containerWidth = "512";
        var camera, renderer, container, scene, texture, material, geometry,
            cube;

        var raycaster = new THREE.Raycaster();
        var mouse = new THREE.Vector2();
        var onClickPosition = new THREE.Vector2();

        init();
        animate();


        /**
         * Configurator init function
         */

        function init() {

            /**
             * Camera
             */

            camera = new THREE.PerspectiveCamera( 30, window.innerWidth / window.innerHeight, 0.01, 100 );
            camera.position.set( 0, 0, 3.5 );


            /**
             * Renderer
             */

            container = document.getElementById( "renderer" );
            renderer = new THREE.WebGLRenderer( { antialias: true } );
            renderer.setPixelRatio( window.devicePixelRatio );
            renderer.setSize( containerWidth, containerHeight );
            camera.aspect = container.clientWidth / container.clientHeight;
            camera.updateProjectionMatrix();
            container.appendChild( renderer.domElement );


            /**
             * Scene
             */

            scene = new THREE.Scene();
            scene.background = new THREE.Color( 0x000000 );


            /**
             * Texture and material
             */

            texture = new THREE.Texture( document.getElementById( "canvas" ) );
            texture.anisotropy = renderer.capabilities.getMaxAnisotropy();

            material = new THREE.MeshBasicMaterial( { map: texture } );


            /**
             * Model
             */

             geometry = new THREE.BoxGeometry( 1, 1, 1 );
             cube = new THREE.Mesh( geometry, material );
             scene.add( cube );
        }


        /**
         * Configurator frame render function
         */

        function animate() {
            requestAnimationFrame( animate );

            cube.rotation.x += 0.004;
            cube.rotation.y += 0.001;
            texture.needsUpdate = true;

            renderer.render( scene, camera );
        }


        /**
         * Listeners
         */

        container.addEventListener( "mousedown", onMouseClick, false );


        /**
         * Other methods
         */

        function onMouseClick( evt ) {
            evt.preventDefault();

            var array = getMousePosition( container, evt.clientX, evt.clientY );
            onClickPosition.fromArray( array );

            var intersects = getIntersects( onClickPosition, scene.children );

            if ( intersects.length > 0 && intersects[ 0 ].uv ) {
                var uv = intersects[ 0 ].uv;
                intersects[ 0 ].object.material.map.transformUv( uv );

                var circle = new fabric.Circle({
                    radius: 3,
                    left: getRealPosition( "x", uv.x ),
                    top: getRealPosition( "y", uv.y ),
                    fill: 'red'
                });
                canvas.add( circle );
            }
        }

        function getRealPosition( axis, value ) {
            let CORRECTION_VALUE = axis === "x"
                                    ? 4.5
                                    : 5.5;

            return Math.round( value * 512 ) - CORRECTION_VALUE;
        }

        var getMousePosition = function ( dom, x, y ) {
            var rect = dom.getBoundingClientRect();
            return [ ( x - rect.left ) / rect.width, ( y - rect.top ) / rect.height ];
        };

        var getIntersects = function ( point, objects ) {
            mouse.set( ( point.x * 2 ) - 1, - ( point.y * 2 ) + 1 );
            raycaster.setFromCamera( mouse, camera );
            return raycaster.intersectObjects( objects );
        };

Please be aware that the above code snippet is sourced from stackoverflow and not authored by me.

Answer №1

In the responses to previous inquiries, there are suggestions on how to replicate mouse events from one canvas onto another:

var simEvt = new MouseEvent(evt.type, {
  clientX: correctedPosition.x,
  clientY: correctedPosition.y
});
canvas.upperCanvasEl.dispatchEvent(simEvt);

However, simply dispatching mousedown, mouseup, and mousemove events on the target canvas is insufficient. This is because when a mousedown event occurs on the canvas, fabric.js immediately switches the mouseup and mousemove listeners to the document instead (most likely to continue tracking those events even if the mouse exits the canvas). As a result, dispatched events on the target canvas are ignored until a mouseup event happens on the document.

To work around this issue, it may be necessary to modify fabric.js internals in order for it to recognize that a mouse event has a different DOM target than the intended canvas. In such cases, adjustments can be made to the x and y positions accordingly.

The following code snippet demonstrates how I patched

fabric.Canvas.prototype.getPointer
:

// ...
if (e.target !== this.upperCanvasEl) {
  var positionOnScene = getPositionOnScene(container, e);
  pointer.x = positionOnScene.x;
  pointer.y = positionOnScene.y;
}
// ...

(getPositionOnScene() is a helper function used to determine the pointer's location within the mirrored canvas; container refers to a DOM element utilized by Three.js for rendering a scene).

I have created a demo incorporating the code mentioned above in response to your initial query: https://codepen.io/shkaper/pen/eYOBQVL

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

Guide on transferring an HTML variable to a PHP script through AJAX

I am currently working on my website where I aim to create a random code that will be passed onto a PHP file for future retrieval. However, the code seems to be malfunctioning. Take a look at the code: Javascript/HTML: function init() { var code ...

Extract the URL from an AJAX request within an IFRAME using a Chrome Extension

Currently, I am facing the following issue: I am trying to develop a Chrome extension, but encountering some major obstacles: The page I am working with loads an iframe. This iframe contains JavaScript code that triggers an AJAX request which returns an ...

What could be causing the issue with updating a js file using ajax?

I've been dealing with a php file called users. Initially, everything was going smoothly as I wrote some JavaScript code for it. However, after making updates to the JavaScript code, it seems to have stopped functioning. Below is the content of the p ...

Using JavaScript to set the value of an input text field in HTML is not functioning as expected

I am a beginner in the programming world and I am facing a minor issue My challenge lies with a form called "fr" that has an input text box labeled "in" and a variable "n" holding the value of "my text". Below is the code snippet: <html> <head&g ...

Create a new array by dynamically generating a key while comparing two existing arrays

One of the features in my app involves retrieving data from an API and storing it in $scope.newz. The previous user activity is loaded from LocalStorage as bookmarkData. I am facing a challenge with comparing the contentId values in arrays $scope.newz an ...

Utilize ASP.Net to Retrieve and Showcase RSS Feeds

Looking to read and display a specific feed on my website. Developed in C# using .NET 2. Attempted to follow this tutorial: Encountered the following error: A column named 'link' already belongs to this DataTable: cannot set a nested table n ...

Exploring the (*ngFor) Directive to Iterate Through an [object Object]

Attempting to iterate through the array using *ngFor as shown below. let geographicalArea = [{ "_id": "5e77f43e48348935b4571fa7", "name": "Latin America", "employee": { "_id": "5e77c50c4476e734d8b30dc6", "name": "Thomas", ...

What is the best way to retrieve the total number of nested objects within an object?

I'm trying to figure out how to get the number of nested objects in the object a. a = { firstNested: { name: 'George' } secondNested: { name: 'James' } } I thought about using .length, which is typ ...

Enrich your HTML using AngularJS

CSS <div class="container"> <section> <p>{{content}}</p> </section> </div> script.js (function () { var script = function ($scope){ $scope.content = "example"; } }()); I am currently ...

Customize the text displayed in a dropdown menu in Angular Material based on the selection made

I am working with a multi-select dropdown menu that includes an option labeled "ALL" which, when selected, chooses all available options in the list. My goal is to display "ALL" in the view when this option is chosen or when the user manually selects all t ...

What is the best way to format a text component so that the initial word in each sentence is bolded?

Creating a text component where the first word of the sentence is bold can be a bit tricky. The current solution may result in a messy output like "Tips: favouritevacation" where there is no space after "Tips:". This approach is not very elegant. One pos ...

Set an enumerated data type as the key's value in an object structure

Here is an example of my custom Enum: export enum MyCustomEnum { Item1 = 'Item 1', Item2 = 'Item 2', Item3 = 'Item 3', Item4 = 'Item 4', Item5 = 'Item 5', } I am trying to define a type for the f ...

What could be the reason that my Javascript function is not displaying in the HTML document?

I'm currently working on developing an HTML page that displays random quotes by Mark Twain. The function and array of quotes are set up in a separate Javascript file which is linked to the main HTML document. However, I am facing an issue where the ou ...

JavaScript throws an error when attempting to access an object's methods and attributes

Within my Angular.js module, I have defined an object like this: $scope.Stack = function () { this.top = null; this.size = 0; }; However, when I try to use the push method of this object, I encounter an error stating undefined: ...

What is the best way to prevent a modal from being triggered specifically on mobile devices such as phones and

$(function() { $('.pop').on('click', function() { $('.imagepreview').attr('src', $(this).find('img').attr('src')); $('#imagemodal').modal('show'); }); }); .modal-ba ...

How can we develop a strategy to select products based on specific features while keeping costs minimized?

I've got a collection of products with varying costs and features. Each product offers a unique set of features. I'm in need of an algorithm that, when given the specific features I desire, can recommend the most cost-effective combination of pr ...

Issue with VueJS 2 and TypeScript: computed value unable to recognize property specified in data object

When creating the following component: <template lang="html"> <div> <p>{{ bar }}</p> </div> </template> <script lang="ts"> import Vue from 'vue'; export const FooBar = Vue.ex ...

Selenium testing - Immediate disappearance of OK/Cancel popup

Something strange is happening here. When I manually test this, I click the delete button, wait for the popup to appear https://i.sstatic.net/6PMS4.png and then click OK to remove the record successfully. However, when I attempt the same process in Java/Se ...

`Nextjs customizes the position of locales`

Currently implementing i18n translation in my project, the default root appears as follows: https://example.com/en/business/transaction Is it possible to customize the root to appear as: https://example.com/business/en/transacation Thank you. ...

When a legend is clicked, it should display only the selected item while hiding all other legends automatically in a chart created

I have a highchart with 10 legends. When I click on the first legend, only that legend should remain visible while the rest are automatically hidden... Below is a code snippet with two legends: $(function() { var chart = $('#container').hig ...