我用 PixiJS 撸了个圆桌会议选座系统,从 0 到 1 踩坑全复盘
大家好,我是写了 10 年代码的老前端,最近接了个需求:做一个圆桌会议可视化选座系统。
一、需求拆解:圆桌会议到底要什么?
先把需求扒干净,避免做无用功:
- 形态:中间是圆桌,座位沿圆周均匀分布,绝对不能重叠
- 交互:点击选座 / 取消、拖拽换位、保存布局(刷新不丢)
- 性能:座位最多 20 个,要流畅拖拽,不能卡顿
- 兼容:PixiJS 版本坑多,要兼容 v6/v7 所有版本
核心难点:
- 长方桌改圆桌:坐标计算从「上下左右」变成「极坐标 + 角度」
- 避免重叠:必须用圆周均分算法,不能手动硬编码
- PixiJS API 差异:
getGlobalPosition在不同版本里写法不一样,很容易踩坑
二、技术选型:为什么选 PixiJS 而不是 Konva?
我做过 Konva 版,也对比过 Fabric.js,最后选 PixiJS 的原因很简单:
- 性能更强:PixiJS 是 WebGL 渲染,大量座位时帧率更稳
-
分层更灵活:用
Container做基础层 + 拖拽层,性能损耗极小 - 社区成熟:大厂可视化项目都在用,坑都被踩过了
- 轻量:比 Fabric.js 小,比原生 Canvas 开发快 10 倍
三、核心实现:从 0 到 1 搭骨架
1. 初始化 Pixi 应用
先搭好画布和分层容器,这是 Pixi 项目的标准起点:
javascript
运行
const app = new PIXI.Application({
width: 1200,
height: 700,
backgroundColor: 0xf5f5f5,
resolution: window.devicePixelRatio || 1,
antialias: true,
});
document.body.appendChild(app.view);
// 分层:基础层(桌+座位)+ 拖拽层(临时元素)
const baseLayer = new PIXI.Container();
const dragLayer = new PIXI.Container();
app.stage.addChild(baseLayer, dragLayer);
2. 绘制圆桌:从矩形到圆形
把之前的蓝色长方桌换成灰色圆桌,用 drawCircle 实现:
javascript
运行
const TABLE_RADIUS = 180; // 圆桌半径
const CENTER_X = 600; // 画布中心X
const CENTER_Y = 350; // 画布中心Y
const table = new PIXI.Graphics();
table.beginFill(0xCCCCCC);
table.drawCircle(0, 0, TABLE_RADIUS);
table.endFill();
table.x = CENTER_X;
table.y = CENTER_Y;
baseLayer.addChild(table);
3. 环形座位:极坐标计算避免重叠
这是最核心的算法:用极坐标把座位均匀分布在圆周上,彻底解决重叠问题:
javascript
运行
const SEAT_COUNT = 16; // 总座位数
const SEAT_DISTANCE = TABLE_RADIUS + 40; // 座位到圆心的距离
function createSeat(key, index, isOccupied) {
const seat = new PIXI.Graphics();
updateSeatStyle(seat, isOccupied);
// 极坐标转直角坐标:角度 → x/y
const angle = (index / SEAT_COUNT) * Math.PI * 2;
const x = CENTER_X + Math.cos(angle) * SEAT_DISTANCE;
const y = CENTER_Y + Math.sin(angle) * SEAT_DISTANCE;
seat.x = x;
seat.y = y;
seat.rotation = angle + Math.PI/2; // 让座位朝向圆心,更自然
// ... 交互逻辑
}
4. 交互实现:点击 + 拖拽 + 保存
点击选座
直接监听 pointertap 事件,切换座位状态:
javascript
运行
seat.on('pointertap', () => {
seat.isOccupied = !seat.isOccupied;
updateSeatStyle(seat, seat.isOccupied);
// 更新数据数组
});
拖拽换位
PixiJS 拖拽的坑:不同版本获取鼠标坐标的 API 不一样,我封装了一个兼容函数:
javascript
运行
// 兼容 PixiJS v6/v7 的坐标获取
function getGlobalPosition(e) {
if (e.data && typeof e.data.getGlobalPosition === 'function') {
return e.data.getGlobalPosition();
} else if (e.data && e.data.global) {
return e.data.global;
} else {
return app.renderer.plugins.interaction.mouse.global;
}
}
拖拽时在 dragLayer 渲染临时座位,结束后碰撞检测目标座位,交换状态。
保存布局
用 localStorage 持久化座位数据,刷新页面自动加载:
javascript
运行
const STORAGE_KEY = 'roundTableSeats';
let occupiedSeats = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]');
function saveSeatLayout() {
localStorage.setItem(STORAGE_KEY, JSON.stringify(occupiedSeats));
}
四、踩坑复盘:10 年程序员的血泪教训
坑 1:PixiJS 版本 API 不兼容
- 问题:
e.data.getGlobalPosition is not a function - 原因:v6 和 v7 的事件对象结构不一样
- 解决:封装
getGlobalPosition兼容函数,同时锁定 CDN 版本为 v6.5.10(最稳定)
坑 2:座位重叠
- 问题:手动算坐标导致座位挤在一起
- 解决:用极坐标均分算法,
angle = (index / SEAT_COUNT) * Math.PI * 2,保证每个座位间隔一致
坑 3:拖拽卡顿
- 问题:频繁重绘基础层导致帧率掉帧
- 解决:用
dragLayer单独渲染拖拽元素,基础层只在状态变化时重绘
坑 4:CSP 警告
- 问题:浏览器报
upgrade-insecure-requests警告 - 解决:在
<head>加 CSP 元标签,明确允许 PixiJS CDN 和内联脚本
五、完整代码 & 运行方式
直接复制下面的代码,保存为 .html,双击打开就能跑:(这里放你之前的完整圆桌版代码即可)
运行效果
- 中间灰色圆桌,16 个红色 / 灰色座位均匀环绕
- 点击灰色 → 变红(选中),点击红色 → 变灰(取消)
- 拖动红色座位到空座位 → 自动换位
- 点击「保存」→ 刷新页面后选中状态不丢失
六、扩展思路:给产品交差的加分项
-
座位数量动态调整:加个输入框,修改
SEAT_COUNT后重新渲染 - 座位信息编辑:右键菜单,修改座位名称、备注
- 批量操作:框选多个座位,批量移动 / 清空
-
后端对接:把
localStorage换成接口请求,实现多端同步 - 权限控制:不同角色只能选指定区域的座位
七、总结
这次重构让我深刻体会到:
- 可视化项目的核心是坐标计算,圆桌比长方桌难就难在极坐标的理解
- 分层渲染是性能优化的银弹,把频繁更新的元素单独拎出来
- 兼容老版本是前端的宿命,封装兼容函数能少踩 90% 的坑
如果你也在做类似的可视化选座需求,直接拿我的代码改,少走半年弯路。
结尾互动
你在做可视化项目时踩过什么坑?评论区聊聊,我帮你一起解决~预览地址