前端:pageY、clientY、offsetY的区别&拖动效果优化

前言

之前我写过一篇html和JavaScript实现拖动效果的文章,这篇文章里面的东西实在是有些简单了,而且实际上会出现错误,错误效果如下:

这里写图片描述

同样的界面,我们滑动到最上面,效果却是正确的:

这里写图片描述

为什么呢,其实是因为位置的原因,当我们在最上面拖动时,拖动的空间距离顶部的距离是不变的,而当页面滑动到下方时,拖动的时候,页面的位置是不一样的,因此计算出来的也是错误的,引起错误的代码为这其中的clientY:

var list10 = document.getElementById('list10')
function fnMove(event,disX,disY,element){
    var l = event.clientX - disX,
        t = event.clientY - disY
    element.style.left=l+'px'
    element.style.top=t+'px'
}
list10.onmousedown = function(event) {
    event = event || window.event
    var disX = event.clientX - list10.offsetLeft,
    disY = event.clientY - list10.offsetTop;
    list10.style.position = 'fixed'
    document.onmousemove = function(event){
        event = event || window.event;
        fnMove(event,disX,disY,list10);
    }
    document.onmouseup = function(){
        document.onmousemove = null;
    }
}

这里就要讲解一下pageY、clientY、offsetY、layerY的区别了。

pageY、clientY、offsetY的区别

  1. pageY:鼠标在页面上的位置,从页面左上角开始,即是以页面为参考点,不随滑动条移动而变化;
  2. clientY:鼠标在页面上可视区域的位置,从浏览器可视区域左上角开始,即是以浏览器滑动条此刻的滑动到的位置为参考点;
  3. offsetY:IE特有,鼠标相比较于触发事件的元素的位置,以元素盒子模型的内容区域的左上角为参考点,如果有boder,可能出现负值。

这就是我们前面导致错误的原因了,因为我们是采用clientY来计算的,使用client计算的时候,就会由于页面的滚动而计算的位置有错,因此我们这里改为pageY来计算:

var list10 = document.getElementById('list10')
function fnMove(event,disX,disY,element){
    var l = event.clientX - disX,
        t = event.clientY - disY
    element.style.left=l+'px'
    element.style.top=t+'px'
}
list10.onmousedown = function(event) {
    event = event || window.event
    var disX = event.offsetX
    var disY = event.offsetY
    list10.style.left=event.clientX - disX,+'px'
    list10.style.top=event.clientY - disY+'px'
    list10.style.position = 'fixed'
    document.onmousemove = function(event){
        event = event || window.event;
        fnMove(event,disX,disY,list10);
    }
    document.onmouseup = function(){
        document.onmousemove = null;
    }
}

效果如下:

这里写图片描述

源代码

<!DOCTYPE html>
<html>
    <head>
        <title>title</title>
    </head>
    <body>
        <ul>
            <li id="list1">
                1
            </li>
            <li id="list2">
                2
            </li>
            ......
            <li id="list18">
                18
            </li>
            <li id="list19">
                19
            </li>
        </ul>
    </body>
    <script>
        var list10 = document.getElementById('list10')
        function fnMove(event,disX,disY,element){
            var l = event.clientX - disX,
                t = event.clientY - disY
            element.style.left=l+'px'
            element.style.top=t+'px'
        }
        list10.onmousedown = function(event) {
            event = event || window.event
            var disX = event.offsetX
            var disY = event.offsetY
            list10.style.left=event.clientX - disX,+'px'
            list10.style.top=event.clientY - disY+'px'
            list10.style.position = 'fixed'
            document.onmousemove = function(event){
                event = event || window.event;
                fnMove(event,disX,disY,list10);
            }
            document.onmouseup = function(){
                document.onmousemove = null;
            }
        }
    </script>
    <style>
        li {
            background-color: white;
            cursor: pointer;
            padding: 1rem;
            width: 10rem;
            box-shadow: 0px 0px 5px #ccc;
        }
    </style>
</html>
### 实现流程图拖拽功能的方法 为了在前端项目中为流程图组件添加拖拽交互功能,可以采用纯 HTML、CSS 和 JavaScript 的方式,或者借助现代框架(如 React 或 Vue)。以下是详细的解决方案: #### 1. **基于原生 HTML/CSS/JavaScript** 通过 `draggable` 属性以及事件监听器来实现基本的拖拽功能。 ##### HTML 结构 ```html &lt;div id=&quot;flowchart-container&quot; style=&quot;position: relative; width: 800px; height: 600px; border: 1px solid black;&quot;&gt; &lt;div class=&quot;node&quot; draggable=&quot;true&quot;&gt;Node A&lt;/div&gt; &lt;div class=&quot;node&quot; draggable=&quot;true&quot;&gt;Node B&lt;/div&gt; &lt;/div&gt; ``` ##### CSS 样式 ```css .node { position: absolute; width: 100px; height: 50px; line-height: 50px; text-align: center; background-color: lightblue; cursor: grab; } ``` ##### JavaScript 功能 利用 `dragstart`, `dragover`, 和 `drop` 等事件处理程序完成节点移动逻辑。 ```javascript let draggedElement; // 当元素被拖动时触发 document.addEventListener(&#39;dragstart&#39;, (event) =&gt; { event.dataTransfer.setData(&quot;text&quot;, event.target.id); draggedElement = event.target; }); // 阻止默认行为以便允许放置 document.getElementById(&#39;flowchart-container&#39;).addEventListener(&#39;dragover&#39;, (event) =&gt; { event.preventDefault(); }); // 放置元素后的操作 document.getElementById(&#39;flowchart-container&#39;).addEventListener(&#39;drop&#39;, (event) =&gt; { event.preventDefault(); const data = event.dataTransfer.getData(&quot;text&quot;); draggedElement.style.left = `${event.offsetX}px`; draggedElement.style.top = `${event.offsetY}px`; }); ``` 上述代码实现了简单的拖拽效果[^1]。可以通过调整样式和增加更多属性使界面更加复杂化。 --- #### 2. **使用 Vue.js 构建动态流程图** Vue 提供了一种声明式的语法结构,使得状态管理和 DOM 更新变得简单高效。 ##### 组件定义 创建一个可重用的节点组件 `&lt;FlowNode&gt;` 并绑定数据模型。 ```vue &lt;template&gt; &lt;div class=&quot;node&quot; :style=&quot;{left: node.x + &#39;px&#39;, top: node.y + &#39;px&#39;}&quot; @mousedown=&quot;handleMouseDown($event)&quot; draggable=&quot;false&quot;&gt; {{ node.label }} &lt;/div&gt; &lt;/template&gt; &lt;script&gt; export default { props: [&#39;node&#39;], methods: { handleMouseDown(event){ let offsetX = event.clientX - this.node.x, offsetY = event.clientY - this.node.y; document.onmousemove = function(e){ this.node.x = e.clientX - offsetX; this.node.y = e.clientY - offsetY; this.$forceUpdate(); // 强制更新视图 }.bind(this); document.onmouseup = () =&gt; { document.onmousemove = null; document.onmouseup = null; }; } }, }; &lt;/script&gt; ``` ##### 主应用模板 将多个节点渲染到容器内并管理它们的位置信息。 ```vue &lt;template&gt; &lt;div id=&quot;app&quot;&gt; &lt;div id=&quot;flowchart-container&quot; style=&quot;position:relative;width:90vw;height:70vh;border:solid thin gray;&quot;&gt; &lt;FlowNode v-for=&quot;(n, index) in nodes&quot; :key=&quot;index&quot; :node=&quot;n&quot;/&gt; &lt;/div&gt; &lt;/div&gt; &lt;/template&gt; &lt;script&gt; import FlowNode from &#39;./components/FlowNode.vue&#39;; export default { components:{ FlowNode }, data(){ return{ nodes:[ {label:&#39;Start&#39;, x:50,y:50}, {label:&#39;Process&#39;,x:200,y:150}, {label:&#39;End&#39;,x:350,y:250} ] } } } &lt;/script&gt; ``` 此方案展示了如何结合 Vue 数据驱动特性构建灵活易维护的应用场景。 --- #### 3. **React 中实现类似的拖拽机制** React 更加注重组件的状态控制与生命周期钩子函数配合使用。 ##### 节点组件设计 编写独立的功能模块负责单个图形对象的行为表现形式。 ```jsx class Node extends Component { constructor(props){ super(props); this.state={isDragging:false,mouseOffset:{x:0,y:0}}; } onMouseDown=(e)=&gt;{ const offset={ x:e.pageX-this.props.position.x, y:e.pageY-this.props.position.y }; window.addEventListener(&#39;mousemove&#39;,this.handleMouseMove); window.addEventListener(&#39;mouseup&#39;,this.stopDrag); this.setState({isDragging:true,mouseOffset:offset}); }; stopDrag=()=&gt;{ window.removeEventListener(&#39;mousemove&#39;,this.handleMouseMove); window.removeEventListener(&#39;mouseup&#39;,this.stopDrag); this.setState({isDragging:false}); }; handleMouseMove=(e)=&gt;{ if(!this.state.isDragging)return ; const newPosition={ x:e.pageX-this.state.mouseOffset.x, y:e.pageY-this.state.mouseOffset.y }; this.props.updatePosition(newPosition); }; render(){ const styles={ position:&quot;absolute&quot;, left:`${this.props.position.x}px`, top:`${this.props.position.y}px` }; return ( &lt;div className=&quot;node&quot; onPointerDown={this.onMouseDown} style={styles}&gt; {this.props.text} &lt;/div&gt; ); } } export default Node; ``` ##### 容器级协调者角色 集中存储所有节点位置并通过回调接口同步最新坐标值变化情况。 ```jsx const App=()=&gt; { const [nodes,setNodes]=useState([ {id:1,text:&quot;Task One&quot;,pos:{x:100,y:100}}, {id:2,text:&quot;Task Two&quot;,pos:{x:300,y:300}} ]); const updatePos=(newPos,id)=&gt;{ setNodes(prev=&gt;prev.map(n=&gt;{ if(n.id===id){ n.pos=newPos; } return n; })); }; return( &lt;div style={{width:&quot;100%&quot;,height:&quot;calc(100vh - 20px)&quot;,border:&quot;solid&quot;}} &gt; {nodes.map(node=&gt;&lt;Node key={node.id} {...node} updatePosition={(p)=&gt;updatePos(p,node.id)} /&gt;)} &lt;/div&gt; ) }; ReactDOM.render(&lt;App /&gt;,document.querySelector(&#39;#root&#39;)); ``` 以上实例说明了怎样运用 React 思维模式解决实际问题的同时保持良好的性能优化策略. --- ####
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值