You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

问题:仅首个窗口可拖拽,如何让新增窗口支持拖拽?

解决可拖拽弹窗复制后无交互功能的问题

问题根源

  1. DOM元素克隆(cloneNode(true))不会复制通过addEventListener绑定的事件监听器,新生成的窗口自然没有拖拽/缩放功能
  2. 全局的拖拽、缩放函数硬绑定到初始窗口元素,新窗口调用时仍会操作原窗口而非自身
  3. closeWindow函数固定选择#window,无法正确处理非初始窗口的关闭逻辑

修正方案:用类封装窗口行为

将每个窗口的状态、拖拽、缩放、关闭逻辑封装成独立类,确保每个新窗口实例自动拥有完整交互功能:

HTML

<div data-aos="zoom-in" data-aos-delay="300" id="parent">
  <!-- 初始窗口将通过JS创建 -->
</div>

CSS

#parent {
  position: relative;
  width: 800px;
  height: 600px;
  border: 2px solid orange;
}

.old-computer-window {
  position: absolute;
  width: 300px;
  max-height: 1080px;
  background-color: white;
  border-style: solid;
  border-width: 2px;
  border-left-color: #191919;
  border-top-color: #191919;
  border-bottom-color: darkred;
  border-right-color: darkred;
  user-select: none;
  overflow: hidden;
}

.header2 {
  background-color: black;
  color: #fff;
  padding: 10px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  cursor: move;
}

.close {
  cursor: pointer;
  padding: 0 8px;
  border: 2px solid;
  border-left-color: white;
  border-top-color: white;
  border-bottom-color: darkred;
  border-right-color: darkred;
}

.resize-container {
  position: absolute;
  width: 15px;
  height: 15px;
  right: 0;
  bottom: 0;
  cursor: se-resize;
  background-color: rgba(0,0,0,0.1);
}

.image-container {
  height: calc(100% - 40px);
  background-color: #eee;
}

JavaScript

const parentElement = document.querySelector("#parent");
const minimumWidth = 150;
const minimumHeight = 150;

class WindowInstance {
  constructor() {
    this.element = this.createWindowElement();
    this.isDragging = false;
    this.isResizing = false;
    this.xOffset = 0;
    this.yOffset = 0;
    this.bindEvents();
    this.placeRandomly();
    parentElement.appendChild(this.element);
  }

  createWindowElement() {
    const windowEl = document.createElement("div");
    windowEl.classList.add("old-computer-window");
    
    const header = document.createElement("div");
    header.classList.add("header2");
    header.innerHTML = `<p>DANIEL.JPG</p><div class="close">&times;</div>`;
    
    const imageContainer = document.createElement("div");
    imageContainer.classList.add("image-container");
    
    const resizeContainer = document.createElement("div");
    resizeContainer.classList.add("resize-container");
    
    windowEl.appendChild(header);
    windowEl.appendChild(imageContainer);
    windowEl.appendChild(resizeContainer);
    
    return windowEl;
  }

  bindEvents() {
    const header = this.element.querySelector(".header2");
    const resizeContainer = this.element.querySelector(".resize-container");
    const closeBtn = this.element.querySelector(".close");

    // 拖拽事件
    header.addEventListener("mousedown", (e) => this.dragStart(e));
    document.addEventListener("mouseup", () => this.dragEnd());
    document.addEventListener("mousemove", (e) => this.drag(e));

    // 缩放事件
    resizeContainer.addEventListener("mousedown", (e) => this.resizeStart(e));
    document.addEventListener("mouseup", () => this.resizeEnd());
    document.addEventListener("mousemove", (e) => this.resize(e));

    // 关闭事件
    closeBtn.addEventListener("click", () => this.closeWindow());
  }

  dragStart(e) {
    this.initialX = e.clientX - this.xOffset;
    this.initialY = e.clientY - this.yOffset;
    this.isDragging = true;
  }

  dragEnd() {
    this.isDragging = false;
  }

  drag(e) {
    if (!this.isDragging) return;
    e.preventDefault();
    
    this.currentX = e.clientX - this.initialX;
    this.currentY = e.clientY - this.initialY;
    
    this.xOffset = this.currentX;
    this.yOffset = this.currentY;
    
    this.setTranslate(this.currentX, this.currentY);
  }

  setTranslate(xPos, yPos) {
    this.element.style.transform = `translate3d(${xPos}px, ${yPos}px, 0)`;
  }

  resizeStart(e) {
    this.initialX = e.clientX;
    this.initialY = e.clientY;
    this.initialWidth = this.element.offsetWidth;
    this.initialHeight = this.element.offsetHeight;
    this.isResizing = true;
  }

  resizeEnd() {
    this.isResizing = false;
  }

  resize(e) {
    if (!this.isResizing) return;
    
    const currentWidth = this.initialWidth + (e.clientX - this.initialX);
    const currentHeight = this.initialHeight + (e.clientY - this.initialY);
    
    if (currentWidth >= minimumWidth) {
      this.element.style.width = `${currentWidth}px`;
    }
    if (currentHeight >= minimumHeight) {
      this.element.style.height = `${currentHeight}px`;
    }
  }

  placeRandomly() {
    const xPos = Math.floor(Math.random() * (parentElement.offsetWidth - this.element.offsetWidth));
    const yPos = Math.floor(Math.random() * (parentElement.offsetHeight - this.element.offsetHeight));
    this.setTranslate(xPos, yPos);
  }

  closeWindow() {
    this.element.style.display = "none";
    setTimeout(() => {
      // 生成两个新窗口实例
      new WindowInstance();
      new WindowInstance();
      // 移除当前窗口元素
      this.element.remove();
    }, 500);
  }
}

// 创建初始窗口
new WindowInstance();

关键改进点

  • 每个窗口实例拥有独立的状态变量(如isDragging、xOffset),避免全局变量冲突
  • 事件监听器直接绑定到实例方法,确保操作的是当前窗口元素
  • 关闭逻辑自动生成新的窗口实例,新窗口会自动初始化所有交互事件
  • 使用document级别的mouseup和mousemove事件,避免鼠标移出窗口时交互中断

内容的提问来源于stack exchange,提问作者No Penta No Problem

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 21:50:39