一、背景
1、网络售票,需要画座:购票所见即所得
大麦主要业务是票务,包括演唱会、体育赛事、音乐节等,品类繁多。卖票就要画场馆、画座位,大家都在网上买过电影票,这不难理解。虽然可以拿电影售票做类比,但底层难度差异很大。没有10W座的电影院,却有10W座的演唱会,而且演出/体育类场馆变化多,挑战相当大。
2、大麦绘座演进:从示意图到实际场景图
大麦以前的绘座系统,是安装版的程序,画座位只能一个看台一个看台地画,看台之间完全无关联,画出来几乎每个看台都长一个模样,座位只有相对位置的示意图,没有角度、距离,更谈不上精确定位。
图1:老版绘座页面(已淘汰)
大麦网从2017年,开始设计新版绘座系统。这里没有修补,没有重构,新版绘座完全重来,连技术栈也由.NET换成了Java,由C/S换成了B/S。
新绘座以SVG矢量图为核心,通过Canvas进行绘制,在演进的过程中攻克了大量的性能卡点和技术难点,最终打造成型,堪以重任。
图2:新绘座页面
二、新绘座:Flash走了,Canvas来了
1、Flash已死,来到路口,别无选择
新绘座已诞生2年多,现在回首,这条路似乎早已注定。
老版绘座和选座是基于Flash的,悲剧的是,Adobe宣称Flash 2020年后,将不再维护,相关技术会在2020年底全部退役,大麦的绘座和选座系统,都被迫转型。
Flash只是原因之一,看过竞争对手的产品,会发现SVG是条不错的道路,即使没有Flash这一出,大麦网也会朝这个方向迈进。
2、技术选型
1)任何过度使用DOM的应用,都不会快。
经过技术调研,发现国外一些场馆座位绘制,选用的是SVG方案,每个座位都是一个独立的SVG元素。但如果直接把SVG搬到浏览器,无法支持几万座位的场馆,因为浏览器无法支持过多的DOM数量,并且,一旦DOM数量太多,操作一定是低效的,“任何过度使用DOM的应用,都不会太快”。
于是,技术同学想到了Canvas,Canvas是浏览器上的一个画布,无论上面绘制多少元素,对于浏览器而言,都只是一个DOM元素。
对于不了解Canvas的同学,我们可以简单做个说明,Canvas在浏览器上,就是下面一个标签:
在Canvas上绘图,就是使用JS获取Canvas对象,使用封装好的方法进行绘制。Canvas画布上的图形变化,完全通过擦除+重绘的方式展现。
那么新绘座的目标就变得很明确了,我们就是要在Canvas上绘制出想要的场馆座位图,然后以SVG的格式把图形保存起来,用以选座、售票。
2)Canvas也不是银弹:单个Canvas的大小是有限制的,超限之后也会卡顿。
选型初期,技术同学使用Canvas+SVG做了个Demo,模拟了10W座位的渲染,并实现了拖拽、缩放。但真正作为画座组件开发的时候,发现座位达到2W就出现了卡顿,因为Canvas的宽高达到一定的数值,就会出现卡顿。于是,沿着化整为零的思路,技术同学将整个画布,分成了多份Canvas,形成了一个Canvas矩阵,通过对每个Canvas的操作,完美解决了单个Canvas过大引起卡顿的问题。
关于Canvas绘图组件,大家可以在网上搜到很多资料,这里不再赘述。
3、新版绘座上线初期:青苹果
刚上线的新版绘座,就像个青涩的苹果,虽然漂亮,却没那么好吃。
最突出的问题有2个:第1个是变形难用,由于算法比较初级,座位矩阵变形很难满足用户需求;第2个是接口速度慢,打开一个1W座的场馆,好几分钟,超过5W,直接崩溃,根本无法支持。
为什么理想很丰满,结果却差强人意呢?根源在于第一版只重功能,忽略了算法效率。与服务端的接口调用,都是整个场馆级别的,几万座位数据,加上关联的看台、票、以及状态等,一个硕大的数据包在前后端丢来丢去,系统不堪重负,用户受尽折磨。
4、艰苦改进之旅
新绘座上线后,立刻启动了改进优化工程,主要攻克的难关有三点:1. 页面响应时间;2. 座位自由组合变形;3. 打印顺序计算。
1)交互+接口优化,进入秒开时代
首先要解决接口慢的问题,解决效率低的一大法宝:化整为零。
从一次load一个场馆的数据,改成一次load几个看台的数据。服务端数据随着前端视口(页面可视范围)的变化,逐渐加载,类似地图常用的“拉框查询”。前端交互也从全加载,改为按视口取数据。仅此一项优化,几万座大场馆的系统响应速度,立刻由几分钟,降到了1~2s,小场馆更是瞬时打开,系统好用了不少。
这里面最重要的一个技术点,就是视口计算,原理如下:
前端首先获取到屏幕视口在Canvas画布上的坐标,然后和看台的外接矩形进行碰撞检测,两个矩形一旦相交,就说明该看台已暴露在视口之内,于是就加载该看台的数据。
从接口优化开始,新绘座逐渐走向成熟。
图3:按视口加载原理图
2)合并座位矩阵,自由变形
座位自由变形包括倾斜、错位、排距、座距、旋转、弧度等多种操作。除了弧度变形,其它基本上是一些数学上的坐标计算,我们不赘述,这里重点说一下弧度变形。
新弧度变形,运用贝塞尔二阶曲线原理,根据用户的数据输入,计算出相应的贝塞尔曲线,再把每排座位,均匀排列到曲线上。下面是贝塞尔二阶公式:
图4:贝塞尔曲线示意图
注释:P0、P2为一排座位的左右端点(一排的第一个座位和最后一个座位)。
看似套公式就可以搞定,非常简单的样子。但是这里有一个难点:从图中可以看出,t为比例值,处在线段P0P2不同的比例,所在的弧度位置也是不一样的。
如果所有的座位都在P0P2线段上,很好算,但是如果座位之前就是一条弧线呢?
中间所有的座位都不在P0P2线段上,要怎么算出中间座位的每个比例?
我们通过弧线上的每个座位,做一条P0P2线段的垂线,垂线与线段P0P2的交点,就是这些座位所在这一排的原始位置,计算出这些原始位置的坐标,根据这些原始位置,就可以算出中间所有座位的比例了。
这样,弧度变形问题就通过贝塞尔二阶曲线完美解决。
图5:弧形座位矩阵贝塞尔曲线变形原理图
图6:弧度变形实际操作
图7:座位自由组合,随意变形
3) 打印顺序计算
“打印顺序”是个什么鬼?
这得从大麦的业务特点说起,主办有时候会批量出票并将票配发给相关人群,有时整个看台一起打印。在配票的时候,需要按照座位的物理位置关系排序,避免座位没挨着、“2个情侣”被“拆散”的情况发生。举个例子:下图中,主办期望打印票的顺序是“5-3-1-2-4-6”,而不是“1-2-3-4-5-6”,这样他们就可以按打印顺序配票,而不用担心两张票不挨着。那么,在绘座过程中,我们就要计算出座位的顺序,看似简单,实现起来有难度很大,原因只有一个,场馆形状各异,座位排列多样。
图:8:北京奥体中心的某个看台
如果说,上图还能按照座位Y坐标对比进行排序,那么下面的几个情形,就不那么好处理了:
图9:各种特殊的座位排列场景
打印问题,我们通过场景汇总,对场馆进行分区,最终找到了排序的规律,得以解决。打印问题技术方案原理:
第1步:将场馆分成8个象限,象限内的座位,已标识出该如何排序(标识出了应该对比X坐标还是Y坐标来进行排序);
第2步:每一组座位矩阵,取出首排,求首尾座位连线的斜率,然后根据斜率将座位矩阵划分到对应象限;
第3步:按照对应象限的排序标识,对比座位的X坐标(或Y坐标),进行座位排序。
图10:座位排序原理图
4)小彩蛋之“沙发、角度”
效率、变形和打印3个主要问题根解之后,随之出现了大量的产品优化需求,开始着眼于细微之处,小沙发和座位角度就是2个典型的功能。这两个功能虽然难度不大,但却在体验上有了一大步的提升。
图11:圆点、沙发效果对比
5)小彩蛋之“撤回”
经过不断优化和添砖加瓦,大麦的绘座系统,越来越像一款专业的绘图工具。好的绘图工具一定需要“前进&撤回”功能。
新绘座系统的撤回功能实现原理:设计一个“历史数据”数组,数组里的每个元素,记录一个操作步骤对应的被编辑座位数据以及座位位置信息,回退时,找到对应操作步骤的数组元素,重绘座位位置,这样就回退了整个操作。因为无论座位相对位置如何变形,本质上,其实都是坐标数据的改变,通过记录和重绘历史坐标信息,就达到了撤回操作的目的。
三、 在正确的路上继续前行
到目前为止,新绘座系统已能够承接国内外任何大型场馆的绘座工程,各种细节的优化也日臻完善,效率大幅提升。但产品和技术同学的努力,并没有终止,而是在正确的道路上,继续前行。
以下简单列举几个很实用的功能,供大家参考:
1)区域编辑:自由绘制矩形、圆形、多边形等各种形状,并自由变形;
2)一键自动变形:全选看台内的座位,点击“一键变形按钮”,座位瞬间适应看台形状,自动排列。
图12:一键变形效果图
3)座位复制、镜像:区用户可以自由复制选中座位,并且支持镜像、翻转等多种复制模式,排号、座位号根据设置自动处理;
4)一键朝向舞台:用户选中一个看台的数据,点击“一键朝向舞台”,系统会自动计算舞台方向和座位角度,瞬间将整个看台座位“摆正”。