首页
/ GrapesJS 设备管理器(DeviceManager)API 完全指南:多端响应式画布与 CSS 媒体查询的联动原理

GrapesJS 设备管理器(DeviceManager)API 完全指南:多端响应式画布与 CSS 媒体查询的联动原理

2026-09-10 18:52:26作者:冯梦姬Eddie

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,包含:

从源码结构看,设备管理器实现了抽象模块 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):

  • widthMedianull,则回退为 width 的值;
  • widthnull,则回退为 widthMedia 的值;
  • priority 未设置,则取 parseFloat(widthMedia) 作为数值优先级;
  • widthheightwidthMedia 三个属性执行 checkUnit:若传入纯数字(如 900)会自动补上 px 单位,变成 '900px'

这正是文档示例中注释所强调的行为:“没有显式 id 时用 name;name 缺失时生成随机 id”,“width 会应用在画布 frame 和 CSS 媒体上”,而 widthMedia 专门用于媒体查询、height 只影响画布 frame。

模块访问与 API 概览

编辑器实例化后,通过 editor.Devices 获取设备管理器模块:

const deviceManager = editor.Devices;

模块对外提供六个方法,与文档一致:

这些方法的实现均位于 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 上
});

参数

  • propsObject):设备属性,即上文 DeviceProperties 中的字段。
  • optionsRecord<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'}

参数

  • idString):设备 ID。

返回值Devicenull(未找到时)。

源码实现要点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[](设备数组)。实现为直接返回集合的 modelsindex.ts)。

remove 移除设备

const removed = deviceManager.remove('device-id');
// 或直接传入 Device 模型
const device = deviceManager.get('device-id');
deviceManager.remove(device);

参数

  • deviceString | Device):设备 id 或设备模型。
  • opts(可选,默认 {}):附加选项。

返回值:被移除的 Device 模型。

实现委托给内部 __remove,移除后触发 device:remove 事件;测试 index.js 验证了移除数量变化与事件触发。

select 切换当前设备

deviceManager.select('some-id');
// 或传入 Device 模型
const device = deviceManager.get('some-id');
deviceManager.select(device);

参数

  • deviceString | 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:beforedevice:remove:beforedevice:select:before 前缀事件,供需要拦截的场景使用。测试 index.js 中验证了 addremoveupdateselect 各事件以及 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 规则在画布中生效。

实战建议

  • 桌面设备宽度留空:默认配置中 desktopwidth'',表示不限定宽度;同时其 widthMedia 为空,在 CssRule.getDevice() 中被识别为默认设备,桌面端规则不带媒体查询。
  • 区分 widthwidthMedia:需要“画布预览尺寸”与“媒体查询断点”不一致时(例如文档中 device2:画布 800px、断点 810px),必须显式设置 widthMedia;否则两者自动保持一致。
  • 利用 priority 排序媒体查询:多个设备断点接近时,可通过 priority 控制媒体查询的生成顺序,避免样式覆盖顺序出错。
  • 配合编辑器全局配置:媒体查询条件默认是 max-width,可通过编辑器 mediaCondition 配置调整;devicePreviewMode 开启预览模式时 getCurrentMedia() 会返回空字符串,即不再按断点应用媒体样式。

小结

设备管理器通过一套精简的 API(add / get / getDevices / remove / select / getSelected)与清晰的事件体系(device:adddevice:removedevice:selectdevice:update 及 catch-all 的 device),把“设备集合管理”与“画布渲染、媒体查询、CSS 规则”无缝衔接。理解 widthwidthMedia 的语义差异、设备与媒体查询的双向映射,是深度定制 GrapesJS 响应式编辑体验的关键。相关源码与测试分别位于 packages/core/src/device_managerpackages/core/test/specs/device_manager/index.js,可继续深入研读。

热门项目推荐
相关项目推荐

项目优选

收起
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.15 K
2.77 K
kernelkernel
deepin linux kernel
C
34
18
docsdocs
暂无描述
Markdown
900
5.83 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
929
1.85 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.36 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.94 K
1.03 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.47 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
534
603
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
398
leetcodeleetcode
🔥LeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer(第 2 版)》、《程序员面试金典(第 6 版)》题解
Markdown
77
23