Windows 窗口拖拽缩放工具
GB.Windows 为页面中的窗口元素提供拖拽移动、原生尺寸调整和焦点通知能力。它基于 Pointer Events、CSS resize 和 MutationObserver,新加入页面且匹配选择器的窗口也会自动初始化。
安装与引入
浏览器直接引入
<script src="https://cloud.m775.net/js/windows.js"></script>
npm 项目
npm i @gentbo/windows
import '@gentbo/windows';
准备窗口结构
目标窗口应使用可定位布局,并包含一个 header 作为拖拽标题栏。调整尺寸依赖浏览器原生 CSS resize,因此窗口的 overflow 不能是 visible。
<section class="app-window">
<header>
<strong>窗口标题</strong>
<button type="button">关闭</button>
</header>
<main>窗口内容</main>
</section>
.app-window {
position: fixed;
left: 80px;
top: 60px;
width: 720px;
height: 480px;
min-width: 280px;
min-height: 200px;
overflow: hidden;
}
.app-window > header {
cursor: move;
user-select: none;
touch-action: none;
}
快速开始
传入窗口选择器,按需启用缩放、移动和焦点回调,最后调用 start()。
GB.Windows('.app-window')
.resize()
.move()
.focus(windowElement => {
windowElement.classList.add('active');
})
.start();
未调用 resize() 或 move() 时,对应能力不会启用。焦点监听始终会注册,但只有设置有效回调后才执行具体业务。
API 速查
GB.Windows(selector)创建窗口管理器,selector 用于匹配一个或多个窗口元素。.resize()启用浏览器原生双向尺寸调整,并返回当前实例。.move(allowOutside?, margin?)启用标题栏拖拽;margin 按 [上, 右, 下, 左] 设置安全边距,仅在 allowOutside 为 false 时生效。.focus(callback)窗口获得交互焦点时执行 callback(windowElement),并返回当前实例。.start()初始化现有窗口并监听后续 DOM 变化,返回当前实例。.run()start() 的兼容入口,返回当前实例。窗口移动与边界
move() 默认将窗口限制在可用视口内。第二个参数使用 [上, 右, 下, 左] 顺序设置安全边距,四个方向的默认值均为 0。
// 限制在视口内
GB.Windows('.dialog')
.move()
.start();
// 上、右、下、左分别预留 16、24、32、24px
GB.Windows('.workspace-window')
.move(false, [16, 24, 32, 24])
.start();
// 允许拖到视口之外
GB.Windows('.floating-panel')
.move(true) // 第二个参数在此模式下无效
.start();
拖拽从窗口内部的 header 开始。点击标题栏中的 button 不会触发拖拽,因此最小化、最大化和关闭按钮仍可正常操作。
工具不识别任务栏或其他页面组件,可视区域的四向预留空间完全由第二个参数控制。例如需要避开底部栏时,应把底部栏占用的空间作为第三个数组值传入。
调整窗口尺寸
resize() 会为目标窗口设置 resize: both。未允许移出视口时,还会根据窗口当前位置和四向安全边距设置 max-width 与 max-height。
GB.Windows('.resizable-window')
.resize()
.move()
.start();
最小尺寸由窗口自身 CSS 的 min-width 和 min-height 决定。使用原生 resize 时必须设置 overflow: auto、hidden 或 scroll,否则部分浏览器不会显示缩放手柄。
焦点与层级管理
focus() 只通知当前交互的窗口,不会自动修改 z-index。可在回调中维护活动类和窗口层级。
let zIndex = 10;
GB.Windows('.app-window')
.resize()
.move()
.focus(current => {
document.querySelectorAll('.app-window').forEach(item => {
item.classList.toggle('active', item === current);
});
current.style.zIndex = ++zIndex;
})
.start();
窗口内已有的 iframe 也会参与焦点判断:同源 iframe 监听其内部 pointerdown;跨域 iframe 无法访问内部文档时,通过窗口失焦和 activeElement 提供兜底判断。
动态创建窗口
start() 会通过 MutationObserver 监听 body。后续插入并匹配目标选择器的窗口会自动绑定,不需要再次调用。
const windows = GB.Windows('.app-window')
.resize()
.move()
.focus(handleFocus)
.start();
const newWindow = document.createElement('section');
newWindow.className = 'app-window';
newWindow.innerHTML = `
<header>动态窗口</header>
<main>窗口内容</main>
`;
document.body.appendChild(newWindow);
使用边界
启动次数同一管理器不应重复调用 start();重复调用会创建额外的 MutationObserver。销毁能力当前 API 没有 disconnect 或 destroy 方法,适合与页面生命周期一致的窗口系统。标题栏约定移动功能固定查找窗口内部的第一个 header,不支持自定义标题栏选择器。定位方式工具写入 left 和 top;窗口应使用 fixed、absolute 等可定位布局。动态 iframe窗口首次绑定后才插入的 iframe 不会单独补绑内部焦点监听,建议创建好窗口内容后再加入页面。浏览器环境依赖 Pointer Events、MutationObserver、requestAnimationFrame、原生 CSS resize 和 WeakSet。