拖拽的三大事件:
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日的课件