拖拽的三大事件:
onmousedowm / onmousemove / onmouseup
如果把移动事件放在box上,那么当鼠标移动快的时候,鼠标会脱离盒子,导致盒子不跟着鼠标走。
解决:把移动事件放在document上,就能解决
如果把抬起事件放在box上,那么鼠标放到了浏览器的地址栏时,松开鼠标还会导致盒子一直跟着鼠标走。
解决:把抬起事件放在document上
当页面中有文字(图片)并且选中的时候,那么会有浏览器的默认行为(使得拖拽元素拖动和抬起有问题)
解决:在按下的时候阻止默认行为:
DOM 0:return false
DOM2 :ev.preventDefault()
解除事件绑定 :
普通绑定:
ele.onmouseup = null
ES6绑定:
元素 . removeEventListener ( "不带on的事件名",函数名)
ele.removeEventListener("mousemove",this.m)
例子1:拖拽盒子ES6的类+DOM2事件绑定练习
<body> <div id="box" style=" 100px;height:100px;background-color: red;position: absolute" ></div> <script> class Drag{ constructor(id){ this.disX = 0; this.disY = 0; this.box = document.getElementById(id); this.m = this.move.bind(this); this.u = this.up.bind(this) } init(){ this.box.addEventListener("mousedown",this.down.bind(this)) } down(ev){ this.disX = ev.pageX - this.box.offsetLeft; this.disY = ev.pageY - this.box.offsetTop; document.addEventListener("mousemove",this.m); document.addEventListener("mouseup",this.u) } move(ev){ this.box.style.left = ev.pageX - this.disX + "px"; this.box.style.top = ev.pageY - this.disY + "px" } up(){ document.removeEventListener("mousemove",this.m); document.removeEventListener("mouseup",this.u); } } let xxx = new Drag("box"); xxx.init(); </script> </body>
例子2:react 版本 + 加磁吸效果
import React, { Component } from "react"; import "./BaseMoveBox.css"; import PropTypes from "prop-types"; let canRun = true; let timer = null; let time = null; class BaseMoveBox extends Component { constructor(props) { super(props); this.state = { boxsInitialTop: null, boxsInitialLeft: null }; }; componentWillUnmount() { if (timer) { clearTimeout(timer) } if (time) { clearTimeout(time) } } getPosition = (ev) => { const box = document.getElementById(this.props.domId); let {boxsInitialTop, boxsInitialLeft} = this.state; if(!boxsInitialTop) { //存下一个初始值,为磁吸做准备。 boxsInitialTop = box.offsetTop; boxsInitialLeft = box.offsetLeft; this.setState({ boxsInitialTop, boxsInitialLeft }) } const disX = ev.clientX - box.offsetLeft; const disY = ev.clientY - box.offsetTop; return { disX, disY } //鼠标点击的位置,到盒子的左边距,和下边距的距离。 } onMouseDown = (ev) => { if (ev.target.className === "BaseMoveBox_header") { //判断可以拖动的事件源("header"上有其它组件或者按钮,不能触发拖拽) ev.target.parentNode.style.zIndex = 20; ev.preventDefault(); //阻止默认行为(防止拖拽过程中可能选中文字,"box"错误跟随的尴尬) const position = this.getPosition(ev); window.onmousemove = this.onMouseMove; //在 window / document 绑定 onmousemove 和 onmouseup window.onmouseup = this.onMouseUp; this.setState({ disX: position.disX, disY: position.disY }); } } onMouseMove = (ev) => { if (!canRun) return; //防抖 canRun = false; timer = setTimeout(() => { const { disX, disY } = this.state; const x = ev.clientX - disX; const y = ev.clientY - disY; const { clientWidth, clientHeight } = document.documentElement; const box = document.getElementById(this.props.domId); if (box) { //控制 left/top 使"box"不要超出合理的范围 const maxHeight = clientHeight - box.offsetHeight const H = document.getElementsByClassName("page_head")[0].offsetHeight; //减去大标题的高度 const maxWidth = clientWidth - box.offsetWidth; let top = y > H ? (y < maxHeight ? y : maxHeight) : H; let left = x > 0 ? (x < maxWidth ? x : maxWidth) : 0; this.setState({ pageX: left, pageY: top }) } canRun = true; }, 17); } onMouseUp = async (ev) => { window.onmousemove = null; window.onmouseup = null; //磁吸效果 const { boxsInitialTop, boxsInitialLeft } = this.state; const T = boxsInitialTop; const L = boxsInitialLeft; const { pageX, pageY } = this.state; const dT = Math.pow(Math.abs(pageY - T), 2) + Math.pow(Math.abs(pageX - L), 2); const dZ = Math.sqrt(dT); if (dZ < 100) { time = setTimeout(() => { this.setState({ pageX: L, pageY: T }) ev.target.parentNode.style.zIndex = 10; }, 50) } } render() { const { title, width, height, domId, renderDom, backgroundColor } = this.props; const { pageX, pageY } = this.state; // console.log(pageX, pageY) return ( <div className="BaseMoveBox" id={domId} style={{ left: pageX, top: pageY, width, height: height }}> <header className="BaseMoveBox_header" onMouseDown={this.onMouseDown} title={title}>{title}</header> <div className="BaseMoveBox_content" style={{backgroundColor: backgroundColor}}>{renderDom}</div> </div> ) } } BaseMoveBox.propTypes = { T: PropTypes.number, L: PropTypes.number, domId: PropTypes.string, title: PropTypes.string, PropTypes.string, height: PropTypes.string } BaseMoveBox.defaultProps = { domId: `${new Date().getTime() + Math.floor(Math.random() * 1000)}BaseMoveBox`, title: "改变标题,传title", "27.8rem", height: "18.2rem" } export default BaseMoveBox /* * 定位父级很重要 position:"relative" ,基于Window定位 position:fixed * */
// CSS 部分
.BaseMoveBox{
height: 100%;
100%;
position: fixed;
border-style: solid;
border-color: #ccc;
border- 0.05rem;
border-radius: 0.2rem;
/* overflow: hidden; */
z-index: 10;
/*background-color: #E1E3EC */
}
.BaseMoveBox>.BaseMoveBox_header{
.BaseMoveBox>.BaseMoveBox_header{
height: 1.5rem;
line-height: 1.5rem;
padding: 0.1rem 0.5rem;
background-color: #E1E3EC;
cursor: move;
}
.BaseMoveBox>.BaseMoveBox_content{
height: calc(100% - 1.65rem);
100%;
}
更多好玩的效果,看2018年11月20日的课件