新励步课件体系介绍

前言

想必很多“投身于教育行业”的前端工程师们都绕不过“课件”这个话题,对于前端来说,课件项目是教育公司相比互联网公司特有的需求之一,对于公司来说也是及其重要的。目前教育行业我了解到的生产 h5 课件的方式大致分为以下三种,每种方式也是各有优劣,下面是我的理解:

  1. ppt 制作,通过三方或者自研平台转换成 h5
    这种方式可能更适合初创团队或者是开发资源较少的团队,而且一般配合着其他教学服务一起使用(比如直播),这样几乎可以完全不需要技术人员的支持,当然劣势也是明显的,首先这一般要依托于三方平台,其次是编辑端一般需要在 ppt 上完成,文件易丢失和泄漏。
  2. 研发手写课件,比如用 cocos creator 根据教研老师提供的 ppt 生产课件
    这种”流水线“是比较主流的方式之一,往往通过研发手动编写出的课件更加灵活,效果更好更生动,能够完成更复杂的课件。缺点想必很多伙伴也是深有体会,这种方式人力成本巨大,要有一支固定的游戏开发团队,需要教研、设计、研发、测试共同协作才能完成一讲课件。
  3. 提供编辑平台,教研在此平台直接制作并输出 h5 课件
    第三种方式也是比较主流的方式之一,据我所知目前也有很多团队在从第二种方式转到这种方式。这种方式一般是由开发提供课件平台,教研老师自己在平台上制作课件,这样一定程度的释放了开发人员,降低了协作成本,开发课件效率更高,周期更短。当然它也是有劣势的,一般这种编辑平台的开发难度比较高,其次就是前期教研老师对于这个平台的学习成本也不小。

有时候对于团队来说,三种方式不是互斥的,大部分情况三种方式是并行的,会根据内容的类型、复杂度等方面去折中选择。对于我们团队来说,其实也是三种共存的,不过大部分内容生产使用第三种方式,下面我来给大家介绍一下励步课件的技术体系。

业务场景分析及技术难点

介绍技术之前,首先我来简单介绍下我们课件使用的业务场景:

  1. 我们的课件分为两种,线上课件和线下课件,两种课件在内容相互衔接,风格没有明显差异,有区别的是线上课件需要做实时的师生互动,分为师生两端,教师用 PC 端,学生 ipad 居多,线下课主要是线下校区使用利用白板播放,线下除了课件播放,还需要一些辅助教学的功能。
  2. 支持了英语、数学、语文三个学科
  3. 生产线上课件与线下课件是同一个部门的教研老师。

其次我们再来结合业务看下我们要面临的技术难点:

  1. 性能要求高:对于上课的产品,要求是完全接近离线的体验,举例来说,图片、音视频等资源不允许出现缓冲等待;
  2. 容错要求高:灾备方案要考虑,比如断网,课件也需要能够正常播放;
  3. 风险高:对于上课场景来说,基本上一两分钟的系统不可用都是不能忍受的;
  4. 交互复杂:最后一点主要针对编辑端,做过编辑器的伙伴大致都了解,能够实现 ppt 那种功能交互是异常复杂的。

那么结合以上的业务场景和遇到的困难,我来给大家一一介绍我们的处理方式,在此之前为了让大家更好的理解,我先放几张我们整体的功能模块图以及功能演示图。

功能模块图:

课件编辑器:

课件播放器:

编辑器

目前我们的编辑器大致可以分为以下几大模块的功能:

  • 元件系统:这也是我们编辑器的核心功能,支持添加文字、图形、图片、音频、视频以及 iframe,每种元件都支持若干种属性的更改。
  • 互动系统:这其中还分为事件、动画、题型三个模块。

    • 事件模块:我们可以给元件做事件绑定操作,比如我可以给某个元件设置点击事件,点击后隐藏某个另外的元件,或者播放动画、播放音视频,再或者跳转到某一页等等。
    • 动画模块:我们支持自定义动画功能,当前支持折线动画,可以设置播放时长、循环播放等属性
    • 题型模块:课件中可以设置拖拽,选择,连线等题型
  • 通用模块:包括了基础的功能,比如复制粘贴元件,组合,撤销,图片裁剪,帧动画制作,资源库,页码操作等等

那么在实现以上功能的时候,有几个关键的技术点与大家分享。

Canvas vs Dom

相信如果你也做过类似的产品,在初期做技术调研的时候一定也会在 CanvasDom 之间纠结,实际上用两种方式都可以实现这类功能,市面上也都有成功的案例,但我们在开始之前还要针对我们的业务场景来综合评估,首先我们来梳理一下这两种方式的优缺点:
首先对于 Canvas 来说,

  • 优点

    1. 元素多的情况下,性能表现更好
    2. 不需要过多考虑重绘的问题
    3. 对于图片处理更加方便
    4. 三方资源较多
  • 缺点

    1. 上手门槛较高
    2. 元素少的时候会产生无效的画布区域
    3. 不支持音视频、gif 图

其次对于 Dom 来说,

  • 优点

    1. 可以利用 css,元素样式控制方便
    2. 调试方便,可以直接在控制台抓到元素
    3. Dom API 更加完善便捷
  • 缺点

    1. 元素多时性能开销大
    2. 对于不规则图形实现麻烦

那么结合他们各自的优缺点,我们并没有单纯的选取某一种方案,而是把二者结合起来,也就是说两种我们都用了!下面我们来介绍是如何结合使用的。

画布元素 vs 外挂元素

回头再来看一下我们的元件系统,我们可以分为两类,一类是 Canvas 支持的,另一类是不支持的,想必大家也猜到了,对于 Canvas 不支持的元件我们使用了 Dom,总结一下:

  • 使用 Canvas 的元件(画布元素):文本、图形、图片(静态图片)
  • 使用 Dom 的元件(外挂元素):音频、视频、Gif 图、iframe

那么二者在同一个画布上是怎么结合起来的呢?借助下面这个截图来解释一下

原理其实不难,如果我要添加一个外挂元素(音视频、gif、iframe)在画布上,那么在编辑的时候我会把它当做图片来处理,也就是说,用一个图片来在 Canvas 上做占位,我们可以在画布上随意缩放,旋转等,其次我还会同步渲染一个 video 元素盖在画布上层,并且把 canvas 元素的属性翻译成 css 属性,下面列出段伪代码:

/**
 * klass:充当外挂元素的画布元素
 *
 * */
export function getHtmlElement(klass, i, option = {}, evn = 'editor') {
  const basicStyle = {
    display: klass.visible ? 'block' : 'none',
    position: 'absolute',
    transform: `rotate( ${klass.angle}deg )`,
    ...klass.getBoundingRect(),
    // ... 其他公共属性
  };
  switch (klass.type) {
    case 'gif':
      if (klass.angle) {
        const canvasZoom = this.canvas.getZoom();
        const width = (klass.width + klass.strokeWidth) * canvasZoom * klass.scaleX + 1;
        const height = (klass.height + klass.strokeWidth) * canvasZoom * klass.scaleY + 1;
        Object.assign(basicStyle, {
          left: klass.left * canvasZoom - width / 2,
          top: klass.top * canvasZoom - height / 2,
          width: width,
          height: height
        });
      }
      Object.assign(basicStyle, { pointerEvents: 'none' });
      return (
        ![]({klass.getSrc()} />)
      );
    case 'video':
      return(