three.js中iframe鼠标事件异常求助:坐标转换错误
CSS3DRenderer嵌入iframe的鼠标坐标错位问题解决
问题
点击事件正常工作,但拖拽iframe内元素(如视频时间轴)时,鼠标x坐标为实际值的两倍;在其他嵌入网站中,选中文本复制会因y坐标偏移导致页面滚到底部。
复现代码
var camera, scene, renderer; var controls; var Element = function ( id, x, y, z, ry ) { var div = document.createElement( 'div' ); div.style.width = '480px'; div.style.height = '360px'; div.style.backgroundColor = '#000'; var iframe = document.createElement( 'iframe' ); iframe.style.width = '480px'; iframe.style.height = '360px'; iframe.style.border = '0px'; iframe.src = [ 'https://www.youtube.com/embed/', id, '?rel=0' ].join( '' ); div.appendChild( iframe ); var object = new THREE.CSS3DObject( div ); object.position.set( x, y, z ); object.rotation.y = ry; return object; }; init(); animate(); function init() { var container = document.getElementById( 'container' ); camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 1, 5000 ); camera.position.set( 500, 350, 750 ); scene = new THREE.Scene(); renderer = new THREE.CSS3DRenderer(); renderer.setSize( window.innerWidth, window.innerHeight ); renderer.domElement.style.position = 'absolute'; renderer.domElement.style.top = 0; container.appendChild( renderer.domElement ); var group = new THREE.Group(); group.add( new Element( 'xBOqwRRj82A', 0, 0, 240, 0 ) ); group.add( new Element( 'x4q86IjJFag', 240, 0, 0, Math.PI / 2 ) ); group.add( new Element( 'JhngfOK_2-0', 0, 0, - 240, Math.PI ) ); group.add( new Element( 'Grg3461lAPg', - 240, 0, 0, - Math.PI / 2 ) ); scene.add( group ); controls = new THREE.TrackballControls( camera ); controls.rotateSpeed = 4; window.addEventListener( 'resize', onWindowResize, false ); // Block iframe events when dragging camera var blocker = document.getElementById( 'blocker' ); blocker.style.display = 'none'; document.addEventListener( 'mousedown', function () { blocker.style.display = ''; } ); document.addEventListener( 'mouseup', function () { blocker.style.display = 'none'; } ); } function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize( window.innerWidth, window.innerHeight ); } function animate() { requestAnimationFrame( animate ); controls.update(); renderer.render( scene, camera ); }
核心原因
THREE.CSS3DRenderer通过CSS transform对DOM元素进行缩放、旋转变换,但iframe的鼠标事件坐标基于原始文档流坐标,不会自动适配CSS3D变换后的空间,导致坐标映射错位。
解决方案
1. 修正鼠标坐标映射
给每个iframe添加事件监听,将页面坐标转换为CSS3D变换后的局部坐标:
// 修改Element构造函数,添加坐标修正逻辑 var Element = function ( id, x, y, z, ry ) { var div = document.createElement( 'div' ); div.style.width = '480px'; div.style.height = '360px'; div.style.backgroundColor = '#000'; var iframe = document.createElement( 'iframe' ); iframe.style.width = '480px'; iframe.style.height = '360px'; iframe.style.border = '0px'; iframe.src = [ 'https://www.youtube.com/embed/', id, '?rel=0' ].join( '' ); // 监听鼠标事件并修正坐标 const handleMouseEvent = (e) => { e.preventDefault(); const rect = iframe.getBoundingClientRect(); const parentStyle = window.getComputedStyle(div); const transformMatrix = new DOMMatrix(parentStyle.transform); // 计算修正后的坐标(抵消缩放影响) const correctedX = (e.clientX - rect.left) / transformMatrix.a; const correctedY = (e.clientY - rect.top) / transformMatrix.d; // 触发修正后的事件 const correctedEvent = new MouseEvent(e.type, { clientX: correctedX, clientY: correctedY, bubbles: e.bubbles, cancelable: e.cancelable, view: window }); iframe.dispatchEvent(correctedEvent); }; // 绑定需要修正的事件(根据需求添加mousedown/mousemove/mouseup等) iframe.addEventListener('mousedown', handleMouseEvent); iframe.addEventListener('mousemove', handleMouseEvent); iframe.addEventListener('mouseup', handleMouseEvent); div.appendChild( iframe ); var object = new THREE.CSS3DObject( div ); object.position.set( x, y, z ); object.rotation.y = ry; // 保存object引用,用于旋转场景的坐标修正 iframe.dataset.object = object; return object; };
2. 处理3D旋转场景
如果元素有旋转(如示例中的y轴旋转),需添加旋转变换修正:
// 修改handleMouseEvent函数,添加旋转修正 const handleMouseEvent = (e) => { e.preventDefault(); const rect = iframe.getBoundingClientRect(); const parentStyle = window.getComputedStyle(div); const transformMatrix = new DOMMatrix(parentStyle.transform); // 先计算局部坐标 const localX = e.clientX - rect.left; const localY = e.clientY - rect.top; // 处理旋转:将坐标转换为元素未旋转时的原始位置 const object = iframe.dataset.object; const rotationMatrix = new THREE.Matrix3().makeRotationY(object.rotation.y); const vec = new THREE.Vector2(localX, localY).applyMatrix3(rotationMatrix.invert()); // 抵消缩放影响 const correctedX = vec.x / transformMatrix.a; const correctedY = vec.y / transformMatrix.d; // 触发修正事件 const correctedEvent = new MouseEvent(e.type, { clientX: correctedX, clientY: correctedY, bubbles: e.bubbles, cancelable: e.cancelable, view: window }); iframe.dispatchEvent(correctedEvent); };
3. 优化相机拖拽的事件拦截
原全局事件拦截逻辑会干扰iframe交互,改为监听TrackballControls的状态:
// 替换原有的blocker逻辑 var blocker = document.getElementById( 'blocker' ); blocker.style.display = 'none'; controls.addEventListener('start', () => { blocker.style.display = 'block'; }); controls.addEventListener('end', () => { blocker.style.display = 'none'; });
4. 窗口Resize适配
在窗口大小变化时,重新计算元素变换参数,避免坐标错位:
function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); // 遍历所有CSS3DObject,更新变换参数缓存 scene.traverse(obj => { if (obj instanceof THREE.CSS3DObject && obj.children[0].tagName === 'IFRAME') { const iframe = obj.children[0]; const rect = iframe.getBoundingClientRect(); iframe.dataset.scaleX = rect.width / 480; iframe.dataset.scaleY = rect.height / 360; } }); }
内容的提问来源于stack exchange,提问作者Сергей Камбур
相关产品推荐
相关产品推荐

