观察者模式 -- 鼠标拖拽div
观察者模式:定义了对象间一种一对多的依赖关系,当目标对象 Subject 的状态发生改变时,所有依赖它的对象 Observer 都会得到通知。
以观察者模式的形式去实现鼠标拖拽div。
注:此处的实现是一对一的依赖关系。
代码实现
基本html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>test</title>
<style>
* {
padding: 0;
margin: 0;
}
#app {
width: 100%;
height: 100%;
background-color: lightyellow;
}
#box {
width: 100px;
height: 100px;
position: absolute;
background-color: lightskyblue;
left: 0;
top: 0;
}
</style>
</head>
<body>
<div id="app">
<div id="box"></div>
</div>
<script src="./src/observer.js"></script>
</body>
</html>
我们有一个目标者类,管理鼠标的状态。
class Subject {
constructor () {
// 鼠标未拖拽物体时状态为0
this.moveState = 0
// 未指定观察者
this.observer = null
}
getState() {
return this.moveState
}
// 鼠标状态改变时发布通知notify,通知观察者进行活动
setState(moveState) {
this.moveState = moveState
this.notify()
}
notify () {
//...
}
// 指定观察者
attach (observer) {
this.observer = observer
}
}
有一个观察者类,等待目标者类的通知进行活动。
class Observer {
constructor(el, subject) {
//...
this.subject = subject
this.subject.attach(this)
}
// 接到目标者通知时进行update活动
update () {
//...
}
}
我们的目的是要达到观察者观察目标者,当目标者的moveState状态更新后观察者才可进行活动,即鼠标进入拖拽状态时,id为box的div才跟着鼠标运动。
为了实现上述,Observer类与Subject类如下
/* 目标者类 */
class Subject {
constructor () {
this.moveState = 0
this.observer = null
}
getState() {
return this.moveState
}
setState(moveState) {
this.moveState = moveState
this.notify()
}
notify () {
//...
const ob = this.observer
document.addEventListener('mousemove', e => {
// 如果鼠标进入可拖拽状态
if (this.getState()) {
// 计算div应该移动的距离
let moveX = e.clientX - ob.diffX
let moveY = e.clientY - ob.diffY
console.log(moveX)
if (moveX < 0) moveX = 0
else if (moveX > window.innerWidth - ob.el.offsetWidth) moveX = window.innerWidth - ob.el.offsetWidth
if (moveY < 0) moveY = 0
else if (moveY > window.innerHeight - ob.el.offsetHeight) moveY = window.innerHeight - ob.el.offsetHeight
// 通知观察者即div更新其位置
ob.update(moveX, moveY)
}
})
}
attach (observer) {
this.observer = observer
}
}
/* 观察者类 */
class Observer {
constructor(el, subject) {
// 充当观察者的元素
this.el = document.querySelector(el)
// 元素一开始的偏移量
this.offsetX = this.el.offsetLeft
this.offsetY = this.el.offsetTop
// 鼠标移动前后div应该移动的距离
this.diffX = 0
this.diffY = 0
// 指定观察者的目标者
this.subject = subject
this.subject.attach(this)
}
}
测试实例如下
let sbj = new Subject()
let obj = new Observer('#box', sbj)
const el = obj.el
// 当鼠标点击按下时进入可拖拽状态
el.addEventListener('mousedown', e => {
// 进入可拖拽状态
sbj.setState(1)
// 获取div位置,更新div应该移动的距离
obj.offsetX = el.offsetLeft
obj.offsetY = el.offsetTop
obj.diffX = e.clientX - obj.offsetX
obj.diffY = e.clientY - obj.offsetY
})
// 当鼠标点击松开时进入不可拖拽状态
el.addEventListener('mouseup', e => {
sbj.setState(0)
})
完整js代码:
class Subject {
constructor() {
this.moveState = 0
this.observer = null
}
getState() {
return this.moveState
}
setState(moveState) {
this.moveState = moveState
this.notify()
}
notify() {
const ob = this.observer
document.addEventListener('mousemove', e => {
if (this.getState()) {
let moveX = e.clientX - ob.diffX
let moveY = e.clientY - ob.diffY
if (moveX < 0) moveX = 0
else if (moveX > window.innerWidth - ob.el.offsetWidth) moveX = window.innerWidth - ob.el.offsetWidth
if (moveY < 0) moveY = 0
else if (moveY > window.innerHeight - ob.el.offsetHeight) moveY = window.innerHeight - ob.el.offsetHeight
ob.update(moveX, moveY)
}
})
}
attach(observer) {
this.observer = observer
}
}
class Observer {
constructor(el, subject) {
this.el = document.querySelector(el)
this.offsetX = this.el.offsetLeft
this.offsetY = this.el.offsetTop
this.diffX = 0
this.diffY = 0
this.subject = subject
this.subject.attach(this)
}
update(x, y) {
this.el.style.left = `${x}px`
this.el.style.top = `${y}px`
}
}
let sbj = new Subject()
let obj = new Observer('#box', sbj)
const el = obj.el
el.addEventListener('mousedown', e => {
sbj.setState(1)
obj.offsetX = el.offsetLeft
obj.offsetY = el.offsetTop
obj.diffX = e.clientX - obj.offsetX
obj.diffY = e.clientY - obj.offsetY
})
el.addEventListener('mouseup', e => {
sbj.setState(0)
})