首页 > 解决方案 > 通过坐标 x,y 结构 js 发出点击事件

问题描述

我开始使用three.js 和fabric.js 开发项目,我可以在其中移动和转换3d 模型上的纹理。我在模型上找到了坐标并将其与画布面料 js 上的坐标同步。但我不知道如何模拟点击事件并完全管理这个点击的对象。我找到了说明我的问题的 stackoverflow 主题。非常感谢!

以编程方式从坐标中选择 Fabricjs 画布中的对象

从 Threejs 模型到用于纹理的 Fabricjs 画布的 Raycast 鼠标点击

将点击事件从一个画布发送到另一个画布

代码笔:

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

        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 );
        };

请注意,这段代码不是我的,我只是在 stackoverflow 上找到的,并没有声明作者身份

标签: javascriptthree.jsclickfabricjsraycasting

解决方案


旧问题的一些答案提到了将鼠标事件从一个画布镜像到另一个画布的正确方法:

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

mousedown不幸的是,仅在目标画布上调度、mouseupmousemove事件是不够的。原因是当mousedown在画布上触发事件时,fabric.js 会立即删除mouseupmousemove监听器并将它们重新附加到document(我猜是因为即使在鼠标离开画布后它也想继续监听这些事件)。这意味着即使我们在目标画布上发送事件,fabric.js 也会忽略它们直到moueup被触发document

解决此问题的一种方法是修补 fabric.js 内部,以便能够检测到鼠标事件具有与我们的目标画布不同的 dom 目标,如果发生这种情况,请以合适的方式更正x/位置。y

我正在使用以下代码进行修补fabric.Canvas.prototype.getPointer

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

getPositionOnScene()是计算指针在镜像画布中的位置的辅助方法;container是 Three.js 用于渲染场景的 dom 元素)。

我已经在您的问题中使用笔中的代码进行了演示:https ://codepen.io/shkaper/pen/eYOBQVL


推荐阅读