观察者模式:定义了对象间一种一对多的依赖关系,当目标对象 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
        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
        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)
})

A new frontend developer.