GrapesJS 设备管理器(DeviceManager)API 完全指南:多端响应式画布与 CSS 媒体查询的联动原理
GrapesJS 设备管理器(DeviceManager)是驱动响应式 Web 构建的核心模块:它通过一套可配置的“设备(Device)”集合,控制画布 iframe 的显示尺寸,并据此生成对应的 CSS 媒体查询,让同一套组件在不同屏幕宽度下呈现不同布局。本文以 docs/api/device_manager.md 为主干,结合 GrapesJS 仓库源码,系统讲解设备管理器的初始化配置、六个核心 API 方法、事件体系,以及设备宽度与媒体查询、CSS 规则之间的底层联动机制,帮助你掌握在 GrapesJS 中实现多端预览与响应式样式管理的完整方案。
设备管理器是什么
设备管理器是 GrapesJS 中负责“设备(Device)”集合的模块。所谓“设备”,本质上是描述一种屏幕形态的数据对象:它包含名称、宽度、高度等属性。设备管理器维护这个设备集合,并跟踪当前被选中的设备。选中某个设备后,画布中的 frame 会按该设备的宽度/高度重新渲染,同时编辑器的“当前媒体查询(current media)”也随之切换,从而实现“所见即所得”的响应式编辑体验。
在 GrapesJS 的架构中,设备管理器位于 packages/core/src/device_manager,包含:
- 配置项定义:config/config.ts
- 模块入口与 API 实现:index.ts
- 设备数据模型:model/Device.ts、model/Devices.ts
- 设备下拉视图:view/DevicesView.ts
- 事件类型定义:types.ts
从源码结构看,设备管理器实现了抽象模块 ItemManagerModule(见 abstract/Module.ts),因此它具备增、删、查、选等统一的管理能力,同时将设备集合持久化在自身的内存集合中。
初始化与配置
设备管理器允许你从编辑器初始化阶段就定制初始状态,通过 deviceManager 配置对象传入:
const editor = grapesjs.init({
deviceManager: {
// options
},
});
配置对象定义在 packages/core/src/device_manager/config/config.ts,包含两个核心选项:
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
default |
string |
'' |
启动时默认选中的设备 id。若未指定,则使用 devices 列表中第一个设备。 |
devices |
DeviceProperties[] |
内置四个设备(见下) | 默认设备列表。 |
内置的默认设备配置如下:
devices: [
{
id: 'desktop',
name: 'Desktop',
width: '', // 空宽度表示不限制(占满画布)
},
{
id: 'tablet',
name: 'Tablet',
width: '770px',
widthMedia: '992px',
},
{
id: 'mobileLandscape',
name: 'Mobile landscape',
width: '568px',
widthMedia: '768px',
},
{
id: 'mobilePortrait',
name: 'Mobile portrait',
width: '320px',
widthMedia: '480px',
},
],
自定义设备时,可以覆盖 devices 数组,例如:
const editor = grapesjs.init({
deviceManager: {
default: 'tablet',
devices: [
{ id: 'desktop', name: 'Desktop', width: '' },
{ id: 'tablet', name: 'Tablet', width: '900px', widthMedia: '992px' },
{ id: 'mobile', name: 'Mobile', width: '320px', widthMedia: '480px' },
],
},
});
初始化时,模块会遍历 config.devices 逐个调用 add 添加设备(silent: true 不触发事件),随后根据 config.default 或第一个设备完成初始选中(见 index.ts)。
设备属性详解
每个设备由 DeviceProperties 描述,定义于 model/Device.ts:
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
id |
string |
自动生成 | 设备唯一标识。未显式指定时,取 name 作为 id;name 也缺失时生成随机 id。 |
name |
string |
'' |
设备名称,例如 'Mobile',也用于 UI 下拉列表展示。 |
width |
string | null |
null |
应用在画布 iframe 上的宽度,例如 '900px'。 |
height |
string |
'' |
应用在画布 iframe 上的高度,例如 '600px'。 |
minHeight |
string |
未设置 | 画布 iframe 的最小高度。 |
widthMedia |
string | null |
null |
用于 CSS 媒体查询的宽度。若为空,则自动使用 width。 |
priority |
number | null |
null |
媒体查询的排序优先级。 |
注意 Device 模型在初始化(initialize)时会做几项自动归一化(见 model/Device.ts):
- 若
widthMedia为null,则回退为width的值; - 若
width为null,则回退为widthMedia的值; - 若
priority未设置,则取parseFloat(widthMedia)作为数值优先级; - 对
width、height、widthMedia三个属性执行checkUnit:若传入纯数字(如900)会自动补上px单位,变成'900px'。
这正是文档示例中注释所强调的行为:“没有显式 id 时用 name;name 缺失时生成随机 id”,“width 会应用在画布 frame 和 CSS 媒体上”,而 widthMedia 专门用于媒体查询、height 只影响画布 frame。
模块访问与 API 概览
编辑器实例化后,通过 editor.Devices 获取设备管理器模块:
const deviceManager = editor.Devices;
模块对外提供六个方法,与文档一致:
- add:新增设备
- get:按 id 获取设备
- getDevices:获取全部设备
- remove:移除设备
- select:切换当前选中设备
- getSelected:获取当前选中的设备
这些方法的实现均位于 packages/core/src/device_manager/index.ts,下面逐一讲解。
add 新增设备
add 用于向集合中添加新设备,返回新增的 Device 模型:
const device1 = deviceManager.add({
// 没有显式 ID 时,会取 name 作为 ID;name 缺失时生成随机 ID。
id: 'tablet',
name: 'Tablet',
width: '900px', // 该宽度将应用在画布 frame 上,并用于 CSS 媒体查询
});
const device2 = deviceManager.add({
id: 'tablet2',
name: 'Tablet 2',
width: '800px', // 该宽度将应用在画布 frame 上
widthMedia: '810px', // 该宽度将用于 CSS 媒体查询
height: '600px', // 该高度将应用在画布 frame 上
});
参数
props(Object):设备属性,即上文DeviceProperties中的字段。options(Record<string, any>,可选,默认{}):附加选项,例如{ silent: true }可抑制事件触发。
源码实现要点(index.ts):
- 兼容旧版 API:若第一个参数是字符串,则视为
id,第二个参数作为width,第三个参数作为 options; - 若
props中未提供id,则取name;若name也没有,则调用_createId()生成随机 id; - 最终调用
this.devices.add(result, opts)加入集合,并触发device:add事件。
对应测试位于 packages/core/test/specs/device_manager/index.js,其中验证了“无 id 无 name 时自动生成 id”“相同 name 的多次添加返回同一个模型(name 唯一)”等行为。
get 按 ID 获取设备
const device = deviceManager.get('Tablet');
console.log(JSON.stringify(device));
// {name: 'Tablet', width: '900px'}
参数
id(String):设备 ID。
返回值:Device 或 null(未找到时)。
源码实现要点(index.ts):get 会先按 name 匹配(遍历集合比对 name 属性),找不到再按 id 从集合中获取。这意味着文档示例中 get('Tablet') 即使传入的是名称也能命中——测试 index.js 专门验证了“id 不同但 name 相同也能取到设备”这一行为。
getDevices 获取全部设备
const devices = deviceManager.getDevices();
console.log(JSON.stringify(devices));
// [{name: 'Desktop', width: ''}, ...]
返回值:Device[](设备数组)。实现为直接返回集合的 models(index.ts)。
remove 移除设备
const removed = deviceManager.remove('device-id');
// 或直接传入 Device 模型
const device = deviceManager.get('device-id');
deviceManager.remove(device);
参数
device(String | Device):设备 id 或设备模型。opts(可选,默认{}):附加选项。
返回值:被移除的 Device 模型。
实现委托给内部 __remove,移除后触发 device:remove 事件;测试 index.js 验证了移除数量变化与事件触发。
select 切换当前设备
deviceManager.select('some-id');
// 或传入 Device 模型
const device = deviceManager.get('some-id');
deviceManager.select(device);
参数
device(String | Device):设备 id 或设备模型。opts(可选,默认{}):附加选项。
作用:切换选中设备,会更新画布中的 frame。这是整个设备管理器影响编辑器状态的核心入口。
源码实现要点(index.ts):select 最终调用 this.em.set('device', md.get('id'), opts),把选中设备的 id 写入编辑器模型的 device 属性。编辑器监听 change:device 后触发 device:select 事件(见 index.ts),而画布与媒体查询系统都会响应这个属性的变化。测试 index.js 验证了:通过 select 或直接 em.set('device', ...) 都能更新选中状态并触发事件。
getSelected 获取当前选中的设备
const selected = deviceManager.getSelected();
返回值:当前选中的 Device 模型。
实现为 this.get(this.em.get('device')),即从编辑器模型的 device 属性反查设备(index.ts)。因此无论通过 select 方法还是其他途径(如 UI 下拉框、em.set('device', ...))改变选中项,getSelected 都会返回一致的结果。
可用事件
设备管理器通过编辑器实例(editor.on(...))对外发布以下事件,事件名定义于 types.ts:
| 事件 | 触发时机 | 回调参数 |
|---|---|---|
device:add |
新增设备 | (device) |
device:remove |
移除设备 | (device) |
device:select |
切换选中设备 | (newDevice, prevDevice) |
device:update |
更新设备属性 | (device, changes, options) |
device |
上述所有事件的 catch-all | ({ event, model, ... }) |
使用示例:
editor.on('device:add', (device) => { ... });
editor.on('device:remove', (device) => { ... });
editor.on('device:select', (newDevice, prevDevice) => { ... });
editor.on('device:update', (device, changes, options) => { ... });
editor.on('device', ({ event, model, ... }) => { ... });
此外,事件枚举中还包含了对应的 device:add:before、device:remove:before、device:select:before 前缀事件,供需要拦截的场景使用。测试 index.js 中验证了 add、remove、update、select 各事件以及 catch-all 事件 all 的触发次数与参数。
与画布、媒体查询的联动原理
设备管理器并不是孤立存在的,其核心价值在于与画布 frame、CSS 媒体查询、CSS 规则的联动:
1. 画布 frame 尺寸
选中设备的 width / height / minHeight 会应用到画布 iframe 上(文档注释明确说明:“This width will be applied on the canvas frame”),实现不同屏幕形态下的预览。设备 UI 下拉视图 view/DevicesView.ts 监听编辑器的 change:device 来同步下拉框选中值,用户切换下拉框时又会调用 em.set('device', ...) 反向驱动编辑器状态。
2. 当前媒体查询文本
编辑器通过 getCurrentMedia() 生成当前媒体查询条件(packages/core/src/editor/model/Editor.ts):
getCurrentMedia() {
const config = this.config;
const device = this.getDeviceModel();
const condition = config.mediaCondition; // 默认 'max-width'
const preview = config.devicePreviewMode; // 预览模式
const width = device && device.get('widthMedia');
return device && width && !preview ? `(${condition}: ${width})` : '';
}
可见:设备选中的 widthMedia 决定了当前媒体查询字符串(如 (max-width: 480px)),编辑器以此为依据对组件应用对应的响应式样式。
3. 媒体查询与设备的双向映射
CSS 规则模型 packages/core/src/css_composer/model/CssRule.ts 提供了 getDevice() 方法,将一条媒体查询规则反查回对应的 Device:
getDevice() {
const { em } = this;
const { atRuleType, mediaText } = this.attributes;
const devices = em?.Devices.getDevices() || [];
const deviceDefault = devices.filter((d) => d.getWidthMedia() === '')[0];
if (atRuleType !== 'media' || !mediaText) {
return deviceDefault || null;
}
return devices.filter((d) => d.getWidthMedia() === getMediaLength(mediaText))[0] || null;
}
这条链路把“设备宽度”与“CSS 媒体查询规则”绑定在一起:widthMedia 为空字符串的设备被视为默认(桌面)设备,媒体查询文本与 widthMedia 一致的设备即为该规则所属设备。这正是设备管理器驱动响应式样式管理的底层实现——选中设备 → 更新 widthMedia 相关媒体查询 → 该设备的专属 CSS 规则在画布中生效。
实战建议
- 桌面设备宽度留空:默认配置中
desktop的width为'',表示不限定宽度;同时其widthMedia为空,在CssRule.getDevice()中被识别为默认设备,桌面端规则不带媒体查询。 - 区分
width与widthMedia:需要“画布预览尺寸”与“媒体查询断点”不一致时(例如文档中device2:画布 800px、断点 810px),必须显式设置widthMedia;否则两者自动保持一致。 - 利用
priority排序媒体查询:多个设备断点接近时,可通过priority控制媒体查询的生成顺序,避免样式覆盖顺序出错。 - 配合编辑器全局配置:媒体查询条件默认是
max-width,可通过编辑器mediaCondition配置调整;devicePreviewMode开启预览模式时getCurrentMedia()会返回空字符串,即不再按断点应用媒体样式。
小结
设备管理器通过一套精简的 API(add / get / getDevices / remove / select / getSelected)与清晰的事件体系(device:add、device:remove、device:select、device:update 及 catch-all 的 device),把“设备集合管理”与“画布渲染、媒体查询、CSS 规则”无缝衔接。理解 width 与 widthMedia 的语义差异、设备与媒体查询的双向映射,是深度定制 GrapesJS 响应式编辑体验的关键。相关源码与测试分别位于 packages/core/src/device_manager 与 packages/core/test/specs/device_manager/index.js,可继续深入研读。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust4.21 K635- DDeepSeek-V4.1-FlashDeepSeek-V4.1-Flash 是一个多模态混合专家(MoE)模型,拥有 5520 亿骨干参数,并支持最多一百万 token 的上下文长度。该模型原生支持图像和文本输入,并以自回归方式生成文本Python70
jforgamejforgame是一个一站式游戏服务器开发框架。包含游戏服务器开发所需要的各种组件,比如网关,socket服务端与客户端,自定义高效消息编解码,游戏热更新,游戏通用工具等等。包含游戏服,跨服,匹配服,后台管理系统等实现,同时提供大量业务案例以供学习。亦可用于其他socket应用,例如及时聊天等。Java161
fizz-gateway-nodeAn Aggregation API Gateway in Java . FizzGate 是一个基于 Java开发的微服务聚合网关,是拥有自主知识产权的应用网关国产化替代方案,能够实现热服务编排聚合、自动授权选择、线上服务脚本编码、在线测试、高性能路由、API审核管理、回调管理等目的,拥有强大的自定义插件系统可以自行扩展,并且提供友好的图形化配置界面,能够快速帮助企业进行API服务治理、减少中间层胶水代码以及降低编码投入、提高 API 服务的稳定性和安全性。Java90
certd开源SSL证书管理工具;全自动证书申请、更新、续期;通配符证书,泛域名证书申请;证书自动化部署到阿里云、腾讯云、主机、群晖、宝塔;https证书,pfx证书,der证书,TLS证书,nginx证书自动续签自动部署JavaScript120
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python300