impress.js 1.0.0版本 源码注释翻译

/*! MIT许可证授权- http://github.com/impress/impress.js * /
/**
* impress.js
*
* impress.js 是一个基于 CSS3转换和转换的强大功能的表示工具
* 在现代浏览器中使用,灵感来自 prezi. com 背后的理念。
*
*
* 版权2011-2012 Bartek Szopka (@bartaz) ,2016-2018 Henrik Ingo (@henrikingo)
*
* 根据MIT授权发布。
*
* ————————————————
* 作者: Bartek Szopka,Henrik Ingo
* 版本: 1.0.0
* 网址: http://impress.js.org
* 来源: http://github.com/impress/impress.js/
*/

// 你是那种喜欢知道里面是怎么运作的人吗?
// 让我给你们展示一下令人印象深刻的东西。
(function (document, window) {
  "use strict";
  var lib;

  // 辅助功能

  // ‘ pfx'是一个接受标准 CSS 属性名作为参数的函数
  // 并返回当前浏览器有效的前缀版本。
  // 这些代码深受 Modernizr  http://www.Modernizr.com/ 的启发
  var pfx = (function () {

    var style = document.createElement("dummy").style,
      prefixes = "Webkit Moz O ms Khtml".split(" "),
      memory = {};

    return function (prop) {
      if (typeof memory[prop] === "undefined") {

        var ucProp = prop.charAt(0).toUpperCase() + prop.substr(1),
          props = (prop + " " + prefixes.join(ucProp + " ") + ucProp).split(" ");

        memory[prop] = null;
        for (var i in props) {
          if (style[props[i]] !== undefined) {
            memory[prop] = props[i];
            break;
          }
        }

      }

      return memory[prop];
    };

  })();

  var validateOrder = function (order, fallback) {
    var validChars = "xyz";
    var returnStr = "";
    if (typeof order === "string") {
      for (var i in order.split("")) {
        if (validChars.indexOf(order[i]) >= 0) {
          returnStr += order[i];

          // 每个 x,y,z 只能使用一次。
          validChars = validChars.split(order[i]).join("");
        }
      }
    }
    if (returnStr) {
      return returnStr;
    } else if (fallback !== undefined) {
      return fallback;
    } else {
      return "xyz";
    }
  };

  // ‘ css'函数将‘ props'对象中给定的样式应用到元素中
  // 以‘ el'命名。 它通过‘ pfx'函数运行所有属性名
  // 确保使用了该属性的正确前缀版本。
  var css = function (el, props) {
    var key, pkey;
    for (key in props) {
      if (props.hasOwnProperty(key)) {
        pkey = pfx(key);
        if (pkey !== null) {
          el.style[pkey] = props[key];
        }
      }
    }
    return el;
  };

  // ‘ translate'为给定数据生成一个 translate 转换字符串。
  var translate = function (t) {
    return " translate3d(" + t.x + "px," + t.y + "px," + t.z + "px) ";
  };

  // ‘ rotate'为给定数据生成一个 rotate 转换字符串。
  // 默认情况下,旋转是按 x y z 顺序进行的,可以通过传递“ true”来恢复
  // 作为第二个参数。
  var rotate = function (r, revert) {
    var order = r.order ? r.order : "xyz";
    var css = "";
    var axes = order.split("");
    if (revert) {
      axes = axes.reverse();
    }

    for (var i = 0; i < axes.length; i++) {
      css += " rotate" + axes[i].toUpperCase() + "(" + r[axes[i]] + "deg)";
    }
    return css;
  };

  // ‘ scale'为给定数据构建一个缩放转换字符串。
  var scale = function (s) {
    return " scale(" + s + ") ";
  };

  // ‘ computedowscale'计算窗口大小和大小之间的比例因子
  // 为配置中的表示定义。
  var computeWindowScale = function (config) {
    var hScale = window.innerHeight / config.height,
      wScale = window.innerWidth / config.width,
      scale = hScale > wScale ? wScale : hScale;

    if (config.maxScale && scale > config.maxScale) {
      scale = config.maxScale;
    }

    if (config.minScale && scale < config.minScale) {
      scale = config.minScale;
    }

    return scale;
  };

  // 检查支持
  var body = document.body;
  var impressSupported =

    // 浏览器应该支持 css3d 转换
    (pfx("perspective") !== null) &&

    // 以及‘ classList'和‘ dataset'api
    (body.classList) &&
    (body.dataset);

  if (!impressSupported) {

    // 我们不能确定‘ classList'是否受到支持
    body.className += " impress-not-supported ";
  }

  // GLOBALS 和默认值

  // 这是所有 impress.js 实例的根元素保存的位置。
  // 是的,这意味着你可以在一个页面上有多个实例,但我没有
  // 确定这在实践中是否有意义;)
  var roots = {};

  var preInitPlugins = [];
  var preStepLeavePlugins = [];

  // 一些默认配置值。
  var defaults = {
    width: 1024,
    height: 768,
    maxScale: 1,
    minScale: 0,

    perspective: 1000,

    transitionDuration: 1000
  };

  // 这只是一个空的函数... 和一个无用的注释。
  var empty = function () { return false; };

  // impresss.js API

  // 这就是有趣的事情开始发生的地方。
  // 这是返回 impresss.js API 的核心‘ impress'函数
  // 用于基于具有给定 id 的元素的表示(“ impress”)
  // 缺省情况下)。
  var impress = window.impress = function (rootId) {

    // 如果浏览器不支持 imps.js,则返回一个虚拟的 API
    // 这可能不是一个完美的解决方案,但是我们回来的早,避免
    // 运行的代码,这些代码可能使用浏览器中未实现的特性。
    if (!impressSupported) {
      return {
        init: empty,
        goto: empty,
        prev: empty,
        next: empty,
        swipe: empty,
        tear: empty,
        lib: {}
      };
    }

    rootId = rootId || "impress";

    // 如果给定的根已经初始化,只需返回 API
    if (roots["impress-root-" + rootId]) {
      return roots["impress-root-" + rootId];
    }

    // gc 库依赖于在对 DOM 进行任何更改之前初始化。
    lib = initLibraries(rootId);

    body.classList.remove("impress-not-supported");
    body.classList.add("impress-supported");

    // 所有演示步骤的数据
    var stepsData = {};

    // 当前活动步骤的元素
    var activeStep = null;

    // 演示文稿的当前状态(位置、旋转和比例)
    var currentState = null;

    // 步骤元素数组
    var steps = null;

    // 配置选项
    var config = null;

    // 浏览器窗口的缩放系数
    var windowScale = null;

    // 根表示元素
    var root = lib.util.byId(rootId);
    var canvas = document.createElement("div");

    var initialized = false;

    // STEP EVENTS
    //
    // 当前由 press.js 触发的两个步骤事件
    // ‘ impress: stepenter'当步骤显示在
    // screen (从前一个转换完成)和
    // ‘ impress: 当步骤离开时触发 stepleave'(
    // 转换到下一步刚刚开始)。

    // 参考最后输入的步骤
    var lastEntered = null;

    // 只要输入 step 元素,就会调用 ‘ onStepEnter'
    // 但是只有当步骤不同于
    // 最后输入的步骤。
    // 我们有时叫‘ goto',因此叫‘ onStepEnter',只是为了重画一个步骤,例如
    // 调整屏幕大小后。 在这种情况下-更准确地说,在任何情况下-我们触发一个
    // ‘ impress: steprefresh‘ event。
    var onStepEnter = function (step) {
      if (lastEntered !== step) {
        lib.util.triggerEvent(step, "impress:stepenter");
        lastEntered = step;
      }
      lib.util.triggerEvent(step, "impress:steprefresh");
    };

    // ‘ onStepLeave'在 currentStep 元素保留时被调用
    // ,但只有当 currentStep 与
    // lastEntered 步骤。
    var onStepLeave = function (currentStep, nextStep) {
      if (lastEntered === currentStep) {
        lib.util.triggerEvent(currentStep, "impress:stepleave", { next: nextStep });
        lastEntered = null;
      }
    };

    // ‘ initStep'通过读取其中的数据来初始化给定的 step 元素
    // 数据属性和设置正确的样式。
    var initStep = function (el, idx) {
      var data = el.dataset,
        step = {
          translate: {
            x: lib.util.toNumber(data.x),
            y: lib.util.toNumber(data.y),
            z: lib.util.toNumber(data.z)
          },
          rotate: {
            x: lib.util.toNumber(data.rotateX),
            y: lib.util.toNumber(data.rotateY),
            z: lib.util.toNumber(data.rotateZ || data.rotate),
            order: validateOrder(data.rotateOrder)
          },
          scale: lib.util.toNumber(data.scale, 1),
          transitionDuration: lib.util.toNumber(
            data.transitionDuration, config.transitionDuration
          ),
          el: el
        };

      if (!el.id) {
        el.id = "step-" + (idx + 1);
      }

      stepsData["impress-" + el.id] = step;

      css(el, {
        position: "absolute",
        transform: "translate(-50%,-50%)" +
          translate(step.translate) +
          rotate(step.rotate) +
          scale(step.scale),
        transformStyle: "preserve-3d"
      });
    };

    // 初始化所有步骤。
    // Read the data-* attributes, store in internal stepsData, and render with CSS.
    var initAllSteps = function () {
      steps = lib.util.$$(".step", root);
      steps.forEach(initStep);
    };

    // ‘ init'API 函数,初始化(并运行)表示。
    var init = function () {
      if (initialized) { return; }
      execPreInitPlugins(root);

      // 首先,我们为移动设备设置视图。
      // 由于某些原因,当 iPad 没有正确使用时,它会变得疯狂。
      var meta = lib.util.$("meta[name='viewport']") || document.createElement("meta");
      meta.content = "width=device-width, minimum-scale=1, maximum-scale=1, user-scalable=no";
      if (meta.parentNode !== document.head) {
        meta.name = "viewport";
        document.head.appendChild(meta);
      }

      // 初始化配置对象
      var rootData = root.dataset;
      config = {
        width: lib.util.toNumber(rootData.width, defaults.width),
        height: lib.util.toNumber(rootData.height, defaults.height),
        maxScale: lib.util.toNumber(rootData.maxScale, defaults.maxScale),
        minScale: lib.util.toNumber(rootData.minScale, defaults.minScale),
        perspective: lib.util.toNumber(rootData.perspective, defaults.perspective),
        transitionDuration: lib.util.toNumber(
          rootData.transitionDuration, defaults.transitionDuration
        )
      };

      windowScale = computeWindowScale(config);

      // 用“ canvas”元素包装步骤
      lib.util.arrayify(root.childNodes).forEach(function (el) {
        canvas.appendChild(el);
      });
      root.appendChild(canvas);

      // 设置初始样式
      document.documentElement.style.height = "100%";

      css(body, {
        height: "100%",
        overflow: "hidden"
      });

      var rootStyles = {
        position: "absolute",
        transformOrigin: "top left",
        transition: "all 0s ease-in-out",
        transformStyle: "preserve-3d"
      };

      css(root, rootStyles);
      css(root, {
        top: "50%",
        left: "50%",
        perspective: (config.perspective / windowScale) + "px",
        transform: scale(windowScale)
      });
      css(canvas, rootStyles);

      body.classList.remove("impress-disabled");
      body.classList.add("impress-enabled");

      // Get 和 init 步骤
      initAllSteps();

      // 设置画布的默认初始状态
      currentState = {
        translate: { x: 0, y: 0, z: 0 },
        rotate: { x: 0, y: 0, z: 0, order: "xyz" },
        scale: 1
      };

      initialized = true;

      lib.util.triggerEvent(root, "impress:init",
        { api: roots["impress-root-" + rootId] });
    };

    // ‘ getStep'是一个辅助函数,它返回一个由参数定义的 step 元素。
    // 如果给定了一个数字,则返回由该数字给定的索引的步骤,如果是字符串
    // 如果给出了 DOM 元素,则返回具有这样 id 的 step 元素
    // 如果它是正确的 step 元素。
    var getStep = function (step) {
      if (typeof step === "number") {
        step = step < 0 ? steps[steps.length + step] : steps[step];
      } else if (typeof step === "string") {
        step = lib.util.byId(step);
      }
      return (step && step.id && stepsData["impress-" + step.id]) ? step : null;
    };

    // 用于重置‘ impress: stepenter'事件的超时
    var stepEnterTimeout = null;

    // ‘ goto'API 函数,作为‘ el'参数(通过索引、 id 或元素)移动到给定的步骤。
    // ‘ duration'可选地作为第二个参数给定,是 css 中的转换持续时间。
    // ‘ reason'是字符串“ next”、“ prev”或“ goto”(默认值) ,将提供给
    // preStepLeave 插件。
    // ‘ origEvent'可能包含导致调用 goto 的事件,例如按键事件
    var goto = function (el, duration, reason, origEvent) {
      reason = reason || "goto";
      origEvent = origEvent || null;

      if (!initialized) {
        return false;
      }

      // 为每个转换重新执行 initAllSteps。 这允许编辑步骤属性
      // 动态地,例如更改它们的坐标,或者甚至删除或添加步骤,并且具有
      // 当 goto ()被调用时,应用这种变化。
      initAllSteps();

      if (!(el = getStep(el))) {
        return false;
      }

      // 有时候通过一些键盘动作可以触发对第一个链接的关注。
      // Browser 在这种情况下试图滚动页面以使该元素可见
      // (即使是身体溢出被设置为隐藏) ,它打破了我们仔细的定位。
      //
      // 所以,作为一个糟糕的(和懒惰的)变通方法,我们将使页面向后滚动到顶部
      // 当选择幻灯片时
      //
      // 如果你正在读这篇文章并且知道更好的处理方法,我很乐意听到关于它!
      window.scrollTo(0, 0);

      var step = stepsData["impress-" + el.id];
      duration = (duration !== undefined ? duration : step.transitionDuration);

      // 如果我们实际上正在进入另一个步骤,那么从执行已注册的
      // preStepLeave 插件。
      if (activeStep && activeStep !== el) {
        var event = { target: activeStep, detail: {} };
        event.detail.next = el;
        event.detail.transitionDuration = duration;
        event.detail.reason = reason;
        if (origEvent) {
          event.origEvent = origEvent;
        }

        if (execPreStepLeavePlugins(event) === false) {

          // PreStepLeave 插件被允许完全中止转换,通过
          // 返回错误。
          // 参见 stop 和 substep plugins 的一个例子
          return false;
        }

        允许插件更改细节值
        el = event.detail.next;
        step = stepsData["impress-" + el.id];
        duration = event.detail.transitionDuration;
      }

      if (activeStep) {
        activeStep.classList.remove("active");
        body.classList.remove("impress-on-" + activeStep.id);
      }
      el.classList.add("active");

      body.classList.add("impress-on-" + el.id);

      // 基于给定的步骤计算画布的目标状态
      var target = {
        rotate: {
          x: -step.rotate.x,
          y: -step.rotate.y,
          z: -step.rotate.z,
          order: step.rotate.order
        },
        translate: {
          x: -step.translate.x,
          y: -step.translate.y,
          z: -step.translate.z
        },
        scale: 1 / step.scale
      };

      // 检查转换是否放大。
      //
      // 这些信息用于改变转换样式:
      // 当我们放大时-我们从移动和旋转过渡开始
      // 缩放被延迟了,但是当我们缩小时,我们就开始了
      // 随着缩小,移动和旋转被延迟。
      var zoomin = target.scale >= currentState.scale;

      duration = lib.util.toNumber(duration, config.transitionDuration);
      var delay = (duration / 2);

      // 如果重新选择相同的步骤,强制计算窗口缩放,
      // 因为它很可能是由窗口大小调整引起的
      if (el === activeStep) {
        windowScale = computeWindowScale(config);
      }

      var targetScale = target.scale * windowScale;

      // 触发当前活动元素的离开(如果它不再是相同的步骤)
      if (activeStep && activeStep !== el) {
        onStepLeave(activeStep, el);
      }

      // 现在我们改变‘ root'和‘ canvas'的转换来触发转换。
      //
      // 这就是为什么有两个元素: ‘ root'和‘ canvas'——它们是
      // 被单独制成动画:
      // ‘ root'用于缩放,‘ canvas'用于翻译和旋转。
      // 它们上的转换是用不同的延迟触发的
      // 视觉效果很好,看起来很自然) ,所以我们需要知道
      // 他们都完了。
      css(root, {

        // 为了使透视图在不同的尺度下看起来相似
        // 我们也需要”扩大”视角
        // 对于 ie11支持,我们必须指定独立的透视图
        // 变换。
        perspective: (config.perspective / targetScale) + "px",
        transform: scale(targetScale),
        transitionDuration: duration + "ms",
        transitionDelay: (zoomin ? delay : 0) + "ms"
      });

      css(canvas, {
        transform: rotate(target.rotate, true) + translate(target.translate),
        transitionDuration: duration + "ms",
        transitionDelay: (zoomin ? 0 : delay) + "ms"
      });

      // 这里有一个棘手的部分..。
      //
      // 如果比例尺没有变化,或者旋转和平移没有变化,这意味着
      // 实际上没有延迟-因为‘ root'或‘ canvas'上没有过渡
      // 元素。 我们希望在正确的时间触发‘印象: 踏步'事件,所以
      // 这里我们比较当前值和目标值,以检查是否应该考虑延迟
      // 帐户。
      //
      // 我知道这种“如果”的说法看起来很可怕,但是当你知道的时候,其实很简单
      // what is going on-it's simply comparing all the values.
      if (currentState.scale === target.scale ||
        (currentState.rotate.x === target.rotate.x &&
          currentState.rotate.y === target.rotate.y &&
          currentState.rotate.z === target.rotate.z &&
          currentState.translate.x === target.translate.x &&
          currentState.translate.y === target.translate.y &&
          currentState.translate.z === target.translate.z)) {
        delay = 0;
      }

      // 存储当前状态
      currentState = target;
      activeStep = el;

      // 这里就是我们触发‘印象: 进步'事件的地方。
      // 我们只需设置一个超时,在转换持续时间(以及可能的延迟)内启动它
      // 考虑到。
      //
      // 我真的想把它做得更优雅一些。 “过渡 / 结束”事件似乎
      // 是最好的方法,但事实上我在两个不同的地方使用了转场
      // 元素,并且‘过渡 / 结束'事件只有在
      // transition (值的更改)导致了一些错误,并使代码真正
      // 很复杂,因为我必须分开处理所有的情况。 现在依然如此
      // 在根本没有转换的情况下需要一个‘ setTimeout'回退。
      // 所以我决定让代码变得更简单,而不是使用闪亮的 new
      // ‘ transitionend'。
      //
      // 如果你想学一些有趣的东西,看看“过渡 / 结束”是如何做到的
      // 回到0.5.2版本的 impress.js:
      //  http://github.com/bartaz/impress.js/blob/0.5.2/js/impress.js
      window.clearTimeout(stepEnterTimeout);
      stepEnterTimeout = window.setTimeout(function () {
        onStepEnter(activeStep);
      }, duration + delay);

      return el;
    };

    // ‘ prev‘ API 函数转到前一步(按文档顺序)
    // ‘ event'是可选的,可能包含导致需要调用 prev ()的事件
    var prev = function (origEvent) {
      var prev = steps.indexOf(activeStep) - 1;
      prev = prev >= 0 ? steps[prev] : steps[steps.length - 1];

      return goto(prev, undefined, "prev", origEvent);
    };

    // ‘ next‘ API 函数进入下一步(按文档顺序)
    // ‘ event'是可选的,可能包含导致需要调用 next ()的事件
    var next = function (origEvent) {
      var next = steps.indexOf(activeStep) + 1;
      next = next < steps.length ? steps[next] : steps[0];

      return goto(next, undefined, "next", origEvent);
    };

    // Swipe for touch devices by@and3rson.
    // 下面我们扩展 api 来控制当前
    // 活动步骤和假定的下一个 / prev 步骤。参见触摸插件
    // 使用这个 api 的示例。

    // Helper 函数
    var interpolate = function (a, b, k) {
      return a + (b - a) * k;
    };

  // 制作刷屏动画。
  //
  // Pct 是介于 -1.0和 + 1.0之间的值,表示当前长度
  // ∮ of the swipe ∮。
  //
  // 如果 pct 为负值,滑动到下一个()步骤,如果为正值,
  // 走向前一步。
  //
  // 请注意,诸如 goto 之类的预先分类插件可能会扰乱
  // next ()和 prev ()步骤,所以我们需要触发预 stepleave 事件
  // 在这里,即使刷一下也不能保证转变真的会发生。
  //
  // 调用 swipe () ,任何值为 pct,本身不会导致
  // 转换发生,这只是动画扫描
  // transition 是提交的——例如在 touchend 事件调用程序中
  // 负责根据需要调用 prev () / next ()。
  //
  // 注意: 现在,这个函数可以被滑动插件使用
  // 是对应的 UI)。 它是一个半内部的 API,故意不是
  //  documentation.md 记录。
  var swipe = function (pct) {
    if (Math.abs(pct) > 1) {
      return;
    }

    // 准备并执行 preStepLeave 事件
    var event = { target: activeStep, detail: {} };
    event.detail.swipe = pct;

    // 将在滑动动画中被忽略,但是为了以防插件想要阅读这个动画,
    // 迁就他们
    event.detail.transitionDuration = config.transitionDuration;
    var idx; // Needed by jshint
    if (pct < 0) {
      idx = steps.indexOf(activeStep) + 1;
      event.detail.next = idx < steps.length ? steps[idx] : steps[0];
      event.detail.reason = "next";
    } else if (pct > 0) {
      idx = steps.indexOf(activeStep) - 1;
      event.detail.next = idx >= 0 ? steps[idx] : steps[steps.length - 1];
      event.detail.reason = "prev";
    } else {

      // 不许动
      return;
    }
    if (execPreStepLeavePlugins(event) === false) {

      // 如果 preStepLeave 插件想终止转换,不要动画滑动
      // 停下来,这样也许可以。 对于子步骤,它自己可能想要做的插件
      // 一些动画,但那不是当前的实现。
      return false;
    }
    var nextElement = event.detail.next;

    var nextStep = stepsData["impress-" + nextElement.id];

    // 如果重新选择相同的步骤,强制计算窗口缩放,
    var nextScale = nextStep.scale * windowScale;
    var k = Math.abs(pct);

    var interpolatedStep = {
      translate: {
        x: interpolate(currentState.translate.x, -nextStep.translate.x, k),
        y: interpolate(currentState.translate.y, -nextStep.translate.y, k),
        z: interpolate(currentState.translate.z, -nextStep.translate.z, k)
      },
      rotate: {
        x: interpolate(currentState.rotate.x, -nextStep.rotate.x, k),
        y: interpolate(currentState.rotate.y, -nextStep.rotate.y, k),
        z: interpolate(currentState.rotate.z, -nextStep.rotate.z, k),

        // 不幸的是,如果旋转顺序改变,就会出现不连续性我能做什么?
        order: k < 0.7 ? currentState.rotate.order : nextStep.rotate.order
      },
      scale: interpolate(currentState.scale * windowScale, nextScale, k)
    };

    css(root, {

      // 为了使透视图在不同的尺度下看起来相似
      // 我们也需要扩大视野
      perspective: config.perspective / interpolatedStep.scale + "px",
      transform: scale(interpolatedStep.scale),
      transitionDuration: "0ms",
      transitionDelay: "0ms"
    });

    css(canvas, {
      transform: rotate(interpolatedStep.rotate, true) +
        translate(interpolatedStep.translate),
      transitionDuration: "0ms",
      transitionDelay: "0ms"
    });
  };

  // 拆除给人留下深刻印象
  // 将 DOM 重置为 impress ()之前的状态。 叫做 init ()。
  // (如果你打电话给 impress (rootId)。 对于多个不同的 rootId 的 init () ,则必须
  // 也称为 tear ()一次为他们每个人
  var tear = function () {
    lib.gc.teardown();
    delete roots["impress-root-" + rootId];
  };

  // 向步骤元素添加一些有用的类。
  //
  // 所有尚未显示的步骤都给出了“未来”类。
  // 当步骤进入时,“将来”类被删除,“现在”类被删除
  // 给出 class。当该步骤被左边的‘ present'类替换为已经过去了。
  //
  // 所以每个 step 元素总是处于三种可能的状态之一:
  //'未来','现在'和'过去'。
  //
  // 在 CSS 中可以使用这些类来对不同类型的步骤进行样式化。
  // 例如,‘ present'类可用于触发某些定制
  // 动画当步骤显示时。
  lib.gc.addEventListener(root, "impress:init", function () {

    // STEP 类别
    steps.forEach(function (step) {
      step.classList.add("future");
    });

    lib.gc.addEventListener(root, "impress:stepenter", function (event) {
      event.target.classList.remove("past");
      event.target.classList.remove("future");
      event.target.classList.add("present");
    }, false);

    lib.gc.addEventListener(root, "impress:stepleave", function (event) {
      event.target.classList.remove("present");
      event.target.classList.add("past");
    }, false);

  }, false);

  // 添加哈希变更支持。
  lib.gc.addEventListener(root, "impress:init", function () {

    // 检测到最后一个散列
    var lastHash = "";

    // ‘ # / step-id'被用来代替‘ # step-id'来防止默认浏览器
    // 滚动到散列中的元素。
    //
    // 而且必须在动画结束后设置,因为在 Chrome 中
    // 使传输延迟。
    // BUG:  http://code.google.com/p/chromium/issues/detail?id=62820
    lib.gc.addEventListener(root, "impress:stepenter", function (event) {
      window.location.hash = lastHash = "#/" + event.target.id;
    }, false);

    lib.gc.addEventListener(window, "hashchange", function () {

      // 当步骤在该位置输入散列时,该位置将被更新
      // (距离这里只有几行) ,所以哈希变化是
      // triggered and we would call‘ goto'again on the same element。
      //
      // 为了避免这种情况,我们存储上次输入的散列并比较。
      if (window.location.hash !== lastHash) {
        goto(lib.util.getElementFromHash());
      }
    }, false);

    // 开始
    // 通过选择 url 中定义的步骤或演示文稿的第一步
    goto(lib.util.getElementFromHash() || steps[0], 0);
  }, false);

  body.classList.add("impress-disabled");

  // 存储并返回给定 impress.js 根元素的 API
  return (roots["impress-root-" + rootId] = {
    init: init,
    goto: goto,
    next: next,
    prev: prev,
    swipe: swipe,
    tear: tear,
    lib: lib
  });

};

// 标志,可以在 JS 中用来检查浏览器是否通过了支持测试
impress.supported = impressSupported;

// ADD 和 INIT 图书馆
// Library factories are defined in src/lib/*.js, and register themselves by calling
// impress.addLibraryFactory (libraryFactoryObject). 它们储存在这里,用来增大
// 在客户端调用 impress 时使用带有库函数的 API (rootId)。
// 参见 src / lib / readme.md 以获得更清晰的示例。
// (高级用法: 对于 rootId 的不同值,libaries 的另一个实例是
// 生成,以防它们需要为不同的根元素保持不同的状态
var libraryFactories = {};
impress.addLibraryFactory = function (obj) {
  for (var libname in obj) {
    if (obj.hasOwnProperty(libname)) {
      libraryFactories[libname] = obj[libname];
    }
  }
};

// 调用每个库工厂,并返回添加到 api 中的 lib 对象。
var initLibraries = function (rootId) { //jshint ignore:line
  var lib = {};
  for (var libname in libraryFactories) {
    if (libraryFactories.hasOwnProperty(libname)) {
      if (lib[libname] !== undefined) {
        throw "impress.js ERROR: Two libraries both tried to use libname: " + libname;
      }
      lib[libname] = libraryFactories[libname](rootId);
    }
  }
  return lib;
};

// ‘ addPreInitPlugin'允许插件注册一个函数
// 在 init 的开始处(同步地)运行,在
// impress () . init ()本身执行。
impress.addPreInitPlugin = function (plugin, weight) {
  weight = parseInt(weight) || 10;
  if (weight <= 0) {
    throw "addPreInitPlugin: weight must be a positive integer";
  }

  if (preInitPlugins[weight] === undefined) {
    preInitPlugins[weight] = [];
  }
  preInitPlugins[weight].push(plugin);
};

// 在 init 的开头调用,以执行所有 pre-init 插件。
var execPreInitPlugins = function (root) { //jshint ignore:line
  for (var i = 0; i < preInitPlugins.length; i++) {
    var thisLevel = preInitPlugins[i];
    if (thisLevel !== undefined) {
      for (var j = 0; j < thisLevel.length; j++) {
        thisLevel[j](root);
      }
    }
  }
};

// ‘ addPreStepLeavePlugin'允许插件注册一个函数
// (同步地)在 goto 的开头运行
impress.addPreStepLeavePlugin = function (plugin, weight) { //jshint ignore:line
  weight = parseInt(weight) || 10;
  if (weight <= 0) {
    throw "addPreStepLeavePlugin: weight must be a positive integer";
  }

  if (preStepLeavePlugins[weight] === undefined) {
    preStepLeavePlugins[weight] = [];
  }
  preStepLeavePlugins[weight].push(plugin);
};

// 在 goto ()的开头调用,以执行所有 preStepLeave 插件。
var execPreStepLeavePlugins = function (event) { //jshint ignore:line
  for (var i = 0; i < preStepLeavePlugins.length; i++) {
    var thisLevel = preStepLeavePlugins[i];
    if (thisLevel !== undefined) {
      for (var j = 0; j < thisLevel.length; j++) {
        if (thisLevel[j](event) === false) {

          // 如果一个插件返回 false,那么 stepleave 事件(以及相关的转换)
          流产了
          return false;
        }
      }
    }
  }
};

} ) (document, window);

// 这就是所有的人!
//
// 谢谢你全都看了。
// 或者感谢你向下滚动阅读最后一部分。
//
// 我在构建 impress.js 时学到了很多,我希望这段代码和注释
// 我会帮助别人至少学到一部分。

/**
* Garbage collection utility
*
* This library allows plugins to add elements and event listeners they add to the DOM. The user
* can call 'impress().lib.gc.teardown()' to cause all of them to be removed from DOM, so that
* the document is in the state it was before calling 'impress().init()'.
*
* In addition to just adding elements and event listeners to the garbage collector, plugins
* can also register callback functions to do arbitrary cleanup upon teardown.
*
* Henrik Ingo (c) 2016
* MIT License
*/

(function (document, window) {
  "use strict";
  var roots = [];
  var rootsCount = 0;
  var startingState = { roots: [] };

  var libraryFactory = function (rootId) {
    if (roots[rootId]) {
      return roots[rootId];
    }

    // 每个根全局变量(实例变量?)
    var elementList = [];
    var eventListenerList = [];
    var callbackList = [];

    recordStartingState(rootId);

    // LIBRARY 功能
    // 我们在结尾作为对象返回的库函数的定义

    // ‘ pushElement'向 gc 堆栈添加一个 DOM 元素
    var pushElement = function (element) {
      elementList.push(element);
    };

    // ‘ appendChild'是一个方便的包装器,它结合了 DOM 的 appendChild 和 gc.pushElement
    var appendChild = function (parent, element) {
      parent.appendChild(element);
      pushElement(element);
    };

    // ‘ pushEventListener'将事件侦听器添加到 gc 堆栈中
    var pushEventListener = function (target, type, listenerFunction) {
      eventListenerList.push({ target: target, type: type, listener: listenerFunction });
    };

    // ‘ addEventListener'将 DOM addEventListener 与 gc.pushEventListener 组合在一起
    var addEventListener = function (target, type, listenerFunction) {
      target.addEventListener(type, listenerFunction);
      pushEventListener(target, type, listenerFunction);
    };

    // ‘ pushCallback'如果上面的实用程序还不够,插件可以添加它们自己的回调
    // 功能做任意的事情。
    var pushCallback = function (callback) {
      callbackList.push(callback);
    };
    pushCallback(function (rootId) { resetStartingState(rootId); });

  // ‘ teardown‘ will
  //-按照后进先出的顺序执行所有回调
  //-按照后进先出顺序对所有 DOM 元素调用‘ removeChild'
  //-按照后进先出的顺序在所有事件侦听器上调用‘ removeEventListener'
  // 拆卸的目标是回到 DOM 以前的状态
  // ‘ impress () . init ()'被称为。
  var teardown = function () {

    // 按照后进先出的顺序执行回调
    var i; // Needed by jshint
    for (i = callbackList.length - 1; i >= 0; i--) {
      callbackList[i](rootId);
    }
    callbackList = [];
    for (i = 0; i < elementList.length; i++) {
      elementList[i].parentElement.removeChild(elementList[i]);
    }
    elementList = [];
    for (i = 0; i < eventListenerList.length; i++) {
      var target = eventListenerList[i].target;
      var type = eventListenerList[i].type;
      var listener = eventListenerList[i].listener;
      target.removeEventListener(type, listener);
    }
  };

  var lib = {
    pushElement: pushElement,
    appendChild: appendChild,
    pushEventListener: pushEventListener,
    addEventListener: addEventListener,
    pushCallback: pushCallback,
    teardown: teardown
  };
  roots[rootId] = lib;
  rootsCount++;
  return lib;
};

// 让 impress core 知道这个库的存在
window.impress.addLibraryFactory({ gc: libraryFactory });

// CORE INIT
// 库工厂(gc (rootId))在 impress (rootId)的开头调用。 Init ()
// 为了拆除的目的,我们可以利用这个机会来拯救国家
// / 在 impress ()之前,DOM 中处于处女状态的一些东西。 Init ()做了什么。
// 注意: 这些也可以通过代码记录在 press.js 核心中,作为这些值
// 正在改变,但是为了不偏离上游太多,我正在补充
// 他们在这里,而不是核心本身。
var recordStartingState = function (rootId) {
  startingState.roots[rootId] = {};
  startingState.roots[rootId].steps = [];

  // 记录步骤是否有 id
  var steps = document.getElementById(rootId).querySelectorAll(".step");
  for (var i = 0; i < steps.length; i++) {
    var el = steps[i];
    startingState.roots[rootId].steps.push({
      el: el,
      id: el.getAttribute("id")
    });
  }

  // 在罕见的多个根的情况下,在 first init ()和
  // 在最后一滴眼泪中复位。
  if (rootsCount === 0) {
    startingState.body = {};

    // 对于作者来说,将 body.class“ impress-not-supported”作为一个开始是一种习惯
    // 值,然后可以通过 impress ()删除它。 Init (). 但这并不是必须的。
    // 记住它是否在那里。
    if (document.body.classList.contains("impress-not-supported")) {
      startingState.body.impressNotSupported = true;
    } else {
      startingState.body.impressNotSupported = false;
    }

    // 如果有一个 meta name"viewport"元素,它的内容将被 init 覆盖
    var metas = document.head.querySelectorAll("meta");
    for (i = 0; i < metas.length; i++) {
      var m = metas[i];
      if (m.name === "viewport") {
        startingState.meta = m.content;
      }
    }
  }
};

// CORE 拆卸
var resetStartingState = function (rootId) {

  // 重置身体元素
  document.body.classList.remove("impress-enabled");
  document.body.classList.remove("impress-disabled");

  var root = document.getElementById(rootId);
  var activeId = root.querySelector(".active").id;
  document.body.classList.remove("impress-on-" + activeId);

  document.documentElement.style.height = "";
  document.body.style.height = "";
  document.body.style.overflow = "";

  // 从根元素和步骤元素中删除样式值
  // 注意: 我们删除了由 impress.js 核心设置的内容。 奥托,我们没有保存任何原件
  // 值。一个更复杂的实现可以跟踪原始值,然后
  // 重置那些。
  var steps = root.querySelectorAll(".step");
  for (var i = 0; i < steps.length; i++) {
    steps[i].classList.remove("future");
    steps[i].classList.remove("past");
    steps[i].classList.remove("present");
    steps[i].classList.remove("active");
    steps[i].style.position = "";
    steps[i].style.transform = "";
    steps[i].style["transform-style"] = "";
  }
  root.style.position = "";
  root.style["transform-origin"] = "";
  root.style.transition = "";
  root.style["transform-style"] = "";
  root.style.top = "";
  root.style.left = "";
  root.style.transform = "";

  // 重置步骤 id (“步骤1” id 是自动生成的)
  steps = startingState.roots[rootId].steps;
  var step;
  while (step = steps.pop()) {
    if (step.id === null) {
      step.el.removeAttribute("id");
    } else {
      step.el.setAttribute("id", step.id);
    }
  }
  delete startingState.roots[rootId];

  // 将 step div 元素移离画布,然后删除画布
  // 注意: 这里有一个隐含的假设,即 canvas div 是唯一的子元素
  // of the root div. 如果还有别的东西,它就会消失。
  var canvas = root.firstChild;
  var canvasHTML = canvas.innerHTML;
  root.innerHTML = canvasHTML;

  if (roots[rootId] !== undefined) {
    delete roots[rootId];
    rootsCount--;
  }
  if (rootsCount === 0) {

    // 在极少数情况下,多个 impress 根元素被初始化,这些
    // 只在所有未初始化时重置。
    document.body.classList.remove("impress-supported");
    if (startingState.body.impressNotSupported) {
      document.body.classList.add("impress-not-supported");
    }

    // 我们需要删除或重置 impress.js 插入的元素
    var metas = document.head.querySelectorAll("meta");
    for (i = 0; i < metas.length; i++) {
      var m = metas[i];
      if (m.name === "viewport") {
        if (startingState.meta !== undefined) {
          m.content = startingState.meta;
        } else {
          m.parentElement.removeChild(m);
        }
      }
    }
  }

};

} ) (document, window);

/**
* Common utility functions
*
* Copyright 2011-2012 Bartek Szopka (@bartaz)
* Henrik Ingo (c) 2016
* MIT License
*/

(function (document, window) {
  "use strict";
  var roots = [];

  var libraryFactory = function (rootId) {
    if (roots[rootId]) {
      return roots[rootId];
    }

    // ‘ $'在‘ context'中返回给定 CSS‘ selector'的第一个元素
    // 给定的元素或整个文档。
    var $ = function (selector, context) {
      context = context || document;
      return context.querySelector(selector);
    };

    // ‘ $$'在‘ context'中返回给定 CSS‘ selector'的元素数组
    // 给定的元素或整个文档。
    var $$ = function (selector, context) {
      context = context || document;
      return arrayify(context.querySelectorAll(selector));
    };

    // ‘ arrayify'获取一个类似数组的对象,并将其转换为真正的 Array
    // 提供所有的样品。原型的优点。
    var arrayify = function (a) {
      return [].slice.call(a);
    };

    // ‘ byId'用给定的‘ id'返回元素——您可能已经猜到了;)
    var byId = function (id) {
      return document.getElementById(id);
    };

    // ‘ getElementFromHash'返回一个由 id 从 hash 部分定位的元素
    // 窗口位置。
    var getElementFromHash = function () {

      // 从 url # 中获取 id,从开头删除‘ #'或‘ # /',
      // 所以“ fallback”“ # slide-id”和“ enhanced”“ # / slide-id”都能工作
      return byId(window.location.hash.replace(/^#\/?/, ""));
    };

    // Throttling 函数调用,Remy Sharp
    //  http://remysharp.com/2010/07/21/throttling-function-calls/ 
    var throttle = function (fn, delay) {
      var timer = null;
      return function () {
        var context = this, args = arguments;
        window.clearTimeout(timer);
        timer = window.setTimeout(function () {
          fn.apply(context, args);
        }, delay);
      };
    };

    // ‘ toNumber'接受作为‘ numeric'参数给定的值,并尝试转动
    // 它变成一个数字。如果不可能,它返回0(或其他值)
    // 被称为‘ fallback')。
    var toNumber = function (numeric, fallback) {
      return isNaN(numeric) ? (fallback || 0) : Number(numeric);
    };

    // ‘ triggerEvent'使用给定的‘ eventName'和‘ detail'数据构建自定义 DOM 事件
    // 并在元素上触发它。
    var triggerEvent = function (el, eventName, detail) {
      var event = document.createEvent("CustomEvent");
      event.initCustomEvent(eventName, true, true, detail);
      el.dispatchEvent(event);
    };

    var lib = {
      $: $,
      $$: $$,
      arrayify: arrayify,
      byId: byId,
      getElementFromHash: getElementFromHash,
      throttle: throttle,
      toNumber: toNumber,
      triggerEvent: triggerEvent
    };
    roots[rootId] = lib;
    return lib;
  };

  // 让 impress core 知道这个库的存在
  window.impress.addLibraryFactory({ util: libraryFactory });

})(document, window);

/**
* Autoplay plugin - Automatically advance slideshow after N seconds
*
* Copyright 2016 Henrik Ingo, henrik.ingo@avoinelama.fi
* Released under the MIT license.
*/
/* global clearTimeout, setTimeout, document */

(function (document) {
  "use strict";

  var autoplayDefault = 0;
  var currentStepTimeout = 0;
  var api = null;
  var timeoutHandle = null;
  var root = null;
  var util;

  // On impress: init,检查是否有默认设置,以及
  // 处理步骤1。
  document.addEventListener("impress:init", function (event) {
    util = event.detail.api.lib.util;

    // 从事件数据获取 API,而不是从全局 impress ()获取 API。 Init ().
    // 你甚至不需要知道根元素的 id 是什么
    // 或任何东西。‘ impress: init'event data gives you everything you
    // 需要控制刚刚初始化的表示。
    api = event.detail.api;
    root = event.target;

    // 以“ data -”开头的 Element 属性,在
    // element.dataset. 此外,使用连字符的单词变为 camelCased。
    var data = root.dataset;

    if (data.autoplay) {
      autoplayDefault = util.toNumber(data.autoplay, 0);
    }

    var toolbar = document.querySelector("#impress-toolbar");
    if (toolbar) {
      addToolbarButton(toolbar);
    }

    api.lib.gc.pushCallback(function () {
      clearTimeout(timeoutHandle);
    });

    // 注意 impress: init 事件之后,也要 impress: stepenter 是
    // 在第一张幻灯片中被触发,这就是代码流继续的地方。
  }, false);

  document.addEventListener("impress:autoplay:pause", function (event) {
    status = "paused";
    reloadTimeout(event);
  }, false);

  document.addEventListener("impress:autoplay:play", function (event) {
    status = "playing";
    reloadTimeout(event);
  }, false);

  // 如果在表示根中定义了默认的自动播放时间,或
  // 在这一步中,设置超时。
  var reloadTimeout = function (event) {
    var step = event.target;
    currentStepTimeout = util.toNumber(step.dataset.autoplay, autoplayDefault);
    if (status === "paused") {
      setAutoplayTimeout(0);
    } else {
      setAutoplayTimeout(currentStepTimeout);
    }
  };

  document.addEventListener("impress:stepenter", function (event) {
    reloadTimeout(event);
  }, false);

  document.addEventListener("impress:substep:enter", function (event) {
    reloadTimeout(event);
  }, false);

  /**
   * Set timeout after which we move to next() step.
   */
  var setAutoplayTimeout = function (timeout) {
    if (timeoutHandle) {
      clearTimeout(timeoutHandle);
    }

    if (timeout > 0) {
      timeoutHandle = setTimeout(function () { api.next(); }, timeout * 1000);
    }
    setButtonText();
  };

  /*** Toolbar plugin integration *******************************************/
  var status = "not clicked";
  var toolbarButton = null;

  var makeDomElement = function (html) {
    var tempDiv = document.createElement("div");
    tempDiv.innerHTML = html;
    return tempDiv.firstChild;
  };

  var toggleStatus = function () {
    if (currentStepTimeout > 0 && status !== "paused") {
      status = "paused";
    } else {
      status = "playing";
    }
  };

  var getButtonText = function () {
    if (currentStepTimeout > 0 && status !== "paused") {
      return "||"; // Pause
    } else {
      return "&#9654;"; // Play
    }
  };

  var setButtonText = function () {
    if (toolbarButton) {

      // 即使标签内容改变,按钮的大小也要保持不变
      var buttonWidth = toolbarButton.offsetWidth;
      var buttonHeight = toolbarButton.offsetHeight;
      toolbarButton.innerHTML = getButtonText();
      if (!toolbarButton.style.width) {
        toolbarButton.style.width = buttonWidth + "px";
      }
      if (!toolbarButton.style.height) {
        toolbarButton.style.height = buttonHeight + "px";
      }
    }
  };

  var addToolbarButton = function (toolbar) {
    var html = '<button id="impress-autoplay-playpause" ' + // jshint ignore:line
      'title="Autoplay" class="impress-autoplay">' + // jshint ignore:line
      getButtonText() + "</button>"; // jshint ignore:line
    toolbarButton = makeDomElement(html);
    toolbarButton.addEventListener("click", function () {
      toggleStatus();
      if (status === "playing") {
        if (autoplayDefault === 0) {
          autoplayDefault = 7;
        }
        if (currentStepTimeout === 0) {
          currentStepTimeout = autoplayDefault;
        }
        setAutoplayTimeout(currentStepTimeout);
      } else if (status === "paused") {
        setAutoplayTimeout(0);
      }
    });

    util.triggerEvent(toolbar, "impress:toolbar:appendChild",
      { group: 10, element: toolbarButton });
  };

})(document);

/**
* Blackout plugin
*
* Press b or . to hide all slides, and b or . again to show them.
* Also navigating to a different slide will show them again (impress:stepleave).
*
* Copyright 2014 @Strikeskids
* Released under the MIT license.
*/
/* global document */

(function (document) {
  "use strict";

  var canvas = null;
  var blackedOut = false;
  var util = null;
  var root = null;
  var api = null;

  // 在等待共享实用程序库时,从 main impress.js 复制这2
  var css = function (el, props) {
    var key, pkey;
    for (key in props) {
      if (props.hasOwnProperty(key)) {
        pkey = pfx(key);
        if (pkey !== null) {
          el.style[pkey] = props[key];
        }
      }
    }
    return el;
  };

  var pfx = (function () {

    var style = document.createElement("dummy").style,
      prefixes = "Webkit Moz O ms Khtml".split(" "),
      memory = {};

    return function (prop) {
      if (typeof memory[prop] === "undefined") {

        var ucProp = prop.charAt(0).toUpperCase() + prop.substr(1),
          props = (prop + " " + prefixes.join(ucProp + " ") + ucProp).split(" ");

        memory[prop] = null;
        for (var i in props) {
          if (style[props[i]] !== undefined) {
            memory[prop] = props[i];
            break;
          }
        }

      }

      return memory[prop];
    };

  })();

  var removeBlackout = function () {
    if (blackedOut) {
      css(canvas, {
        display: "block"
      });
      blackedOut = false;
      util.triggerEvent(root, "impress:autoplay:play", {});
    }
  };

  var blackout = function () {
    if (blackedOut) {
      removeBlackout();
    } else {
      css(canvas, {
        display: (blackedOut = !blackedOut) ? "none" : "block"
      });
      blackedOut = true;
      util.triggerEvent(root, "impress:autoplay:pause", {});
    }
  };

  // 等待 impress.js 初始化
  document.addEventListener("impress:init", function (event) {
    api = event.detail.api;
    util = api.lib.util;
    root = event.target;
    canvas = root.firstElementChild;
    var gc = api.lib.gc;
    var util = api.lib.util;

    gc.addEventListener(document, "keydown", function (event) {

      // Accept b 或.-. 是通过演示文稿远程控制器发送的
      if (event.keyCode === 66 || event.keyCode === 190) {
        event.preventDefault();
        if (!blackedOut) {
          blackout();
        } else {
          removeBlackout();
        }
      }
    }, false);

    gc.addEventListener(document, "keyup", function (event) {

      // Accept b 或.-. 是通过演示文稿远程控制器发送的
      if (event.keyCode === 66 || event.keyCode === 190) {
        event.preventDefault();
      }
    }, false);

  }, false);

  document.addEventListener("impress:stepleave", function () {
    removeBlackout();
  }, false);

})(document);


/**
* Extras Plugin
*
* This plugin performs initialization (like calling mermaid.initialize())
* for the extras/ plugins if they are loaded into a presentation.
*
* See README.md for details.
*
* Copyright 2016 Henrik Ingo (@henrikingo)
* Released under the MIT license.
*/
/* global markdown, hljs, mermaid, impress, document, window */

(function (document, window) {
  "use strict";

  var preInit = function () {
    if (window.markdown) {

      // 与其他额外功能不同,Markdown.js 在默认情况下不做任何内容
      // 特别的,我们在这里自己做。
      // 此外,我们使用“——-”作为新幻灯片的分隔符。

      // Query all. markdown 元素并转换为 HTML
      var markdownDivs = document.querySelectorAll(".markdown");
      for (var idx = 0; idx < markdownDivs.length; idx++) {
        var element = markdownDivs[idx];

        var slides = element.textContent.split(/^-----$/m);
        var i = slides.length - 1;
        element.innerHTML = markdown.toHTML(slides[i]);

        // 如果有一个 id,将其取消设置为 last,所有其他元素,
        // 然后设置为第一个。
        var id = null;
        if (element.id) {
          id = element.id;
          element.id = "";
        }
        i--;
        while (i >= 0) {
          var newElement = element.cloneNode(false);
          newElement.innerHTML = markdown.toHTML(slides[i]);
          element.parentNode.insertBefore(newElement, element);
          element = newElement;
          i--;
        }
        if (id !== null) {
          element.id = id;
        }
      }
    } // Markdown

    if (window.hljs) {
      hljs.initHighlightingOnLoad();
    }

    if (window.mermaid) {
      mermaid.initialize({ startOnLoad: true });
    }
  };

  // 注册要在预 init 阶段调用的插件
  // 注意: Markdown.js 应该先 / 先运行,因为它会创建新的 div 元素。
  // 所以添加一个低于默认权重的值。
  impress.addPreInitPlugin(preInit, 1);

})(document, window);


/**
* Form support
*
* Functionality to better support use of input, textarea, button... elements in a presentation.
*
* This plugin does two things:
*
* Set stopPropagation on any element that might take text input. This allows users to type, for
* example, the letter 'P' into a form field, without causing the presenter console to spring up.
*
* On impress:stepleave, de-focus any potentially active
* element. This is to prevent the focus from being left in a form element that is no longer visible
* in the window, and user therefore typing garbage into the form.
*
* TODO: Currently it is not possible to use TAB to navigate between form elements. Impress.js, and
* in particular the navigation plugin, unfortunately must fully take control of the tab key,
* otherwise a user could cause the browser to scroll to a link or button that's not on the current
* step. However, it could be possible to allow tab navigation between form elements, as long as
* they are on the active step. This is a topic for further study.
*
* Copyright 2016 Henrik Ingo
* MIT License
*/
/* global document */
(function (document) {
  "use strict";
  var root;
  var api;

  document.addEventListener("impress:init", function (event) {
    root = event.target;
    api = event.detail.api;
    var gc = api.lib.gc;

    var selectors = ["input", "textarea", "select", "[contenteditable=true]"];
    for (var selector of selectors) {
      var elements = document.querySelectorAll(selector);
      if (!elements) {
        continue;
      }

      for (var i = 0; i < elements.length; i++) {
        var e = elements[i];
        gc.addEventListener(e, "keydown", function (event) {
          event.stopPropagation();
        });
        gc.addEventListener(e, "keyup", function (event) {
          event.stopPropagation();
        });
      }
    }
  }, false);

  document.addEventListener("impress:stepleave", function () {
    document.activeElement.blur();
  }, false);

})(document);


/**
* Fullscreen plugin
*
* Press F5 to enter fullscreen and ESC to exit fullscreen mode.
*
* Copyright 2019 @giflw
* Released under the MIT license.
*/
/* global document */

(function (document) {
  "use strict";

  function enterFullscreen() {
    var elem = document.documentElement;
    if (!document.fullscreenElement) {
      elem.requestFullscreen();
    }
  }

  function exitFullscreen() {
    if (document.fullscreenElement) {
      document.exitFullscreen();
    }
  }

  // 等待 impress.js 初始化
  document.addEventListener("impress:init", function (event) {
    var api = event.detail.api;
    var root = event.target;
    var gc = api.lib.gc;
    var util = api.lib.util;

    gc.addEventListener(document, "keydown", function (event) {

      // 116(F5)通过演示文稿远程控制器发送
      if (event.code === "F5") {
        event.preventDefault();
        enterFullscreen();
        util.triggerEvent(root.querySelector(".active"), "impress:steprefresh");
      }

      // 27(Escape)通过演示文稿远程控制器发送
      if (event.key === "Escape" || event.key === "F5") {
        event.preventDefault();
        exitFullscreen();
        util.triggerEvent(root.querySelector(".active"), "impress:steprefresh");
      }
    }, false);

    util.triggerEvent(document, "impress:help:add",
      { command: "F5 / ESC", text: "Fullscreen: Enter / Exit", row: 200 });

  }, false);

})(document);


/**
* Goto Plugin
*
* The goto plugin is a pre-stepleave plugin. It is executed before impress:stepleave,
* and will alter the destination where to transition next.
*
* Example:
*
*         <!-- When leaving this step, go directly to "step-5" -->
*         <div class="step" data-goto="step-5">
*
*         <!-- When leaving this step with next(), go directly to "step-5", instead of next step.
*              If moving backwards to previous step - e.g. prev() instead of next() -
*              then go to "step-1". -->
*         <div class="step" data-goto-next="step-5" data-goto-prev="step-1">
*
*        <!-- data-goto-key-list and data-goto-next-list allow you to build advanced non-linear
*             navigation. -->
*        <div class="step"
*             data-goto-key-list="ArrowUp ArrowDown ArrowRight ArrowLeft"
*             data-goto-next-list="step-4 step-3 step-2 step-5">
*
* See https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values for a table
* of what strings to use for each key.
*
* Copyright 2016-2017 Henrik Ingo (@henrikingo)
* Released under the MIT license.
*/
/* global window, document, impress */

(function (document, window) {
  "use strict";
  var lib;

  document.addEventListener("impress:init", function (event) {
    lib = event.detail.api.lib;
  }, false);

  var isNumber = function (numeric) {
    return !isNaN(numeric);
  };

  var goto = function (event) {
    if ((!event) || (!event.target)) {
      return;
    }

    var data = event.target.dataset;
    var steps = document.querySelectorAll(".step");

    // / / / / / / / / / / / / / / 
    if (data.gotoKeyList !== undefined &&
      data.gotoNextList !== undefined &&
      event.origEvent !== undefined &&
      event.origEvent.key !== undefined) {
      var keylist = data.gotoKeyList.split(" ");
      var nextlist = data.gotoNextList.split(" ");

      if (keylist.length !== nextlist.length) {
        window.console.log(
          "impress goto plugin: data-goto-key-list and data-goto-next-list don't match:"
        );
        window.console.log(keylist);
        window.console.log(nextlist);

        // 不要返回,允许其他类别在这个错误的情况下工作
      } else {
        var index = keylist.indexOf(event.origEvent.key);
        if (index >= 0) {
          var next = nextlist[index];
          if (isNumber(next)) {
            event.detail.next = steps[next];

            // 如果新的下一个元素有它自己的 transitionDuration,我们负责
            // 把它也放在活动上
            event.detail.transitionDuration = lib.util.toNumber(
              event.detail.next.dataset.transitionDuration,
              event.detail.transitionDuration
            );
            return;
          } else {
            var newTarget = document.getElementById(next);
            if (newTarget && newTarget.classList.contains("step")) {
              event.detail.next = newTarget;
              event.detail.transitionDuration = lib.util.toNumber(
                event.detail.next.dataset.transitionDuration,
                event.detail.transitionDuration
              );
              return;
            } else {
              window.console.log("impress goto plugin: " + next +
                " is not a step in this impress presentation.");
            }
          }
        }
      }
    }

    // Data-goto-next” & data-goto-prev” / / / / / / / / / / / / / / / / / / / / / 

    // Handle event.target data-goto-next 属性
    if (isNumber(data.gotoNext) && event.detail.reason === "next") {
      event.detail.next = steps[data.gotoNext];

      // 如果新的下一个元素有它自己的 transitionDuration,我们负责设置
      // 在活动中也是如此
      event.detail.transitionDuration = lib.util.toNumber(
        event.detail.next.dataset.transitionDuration, event.detail.transitionDuration
      );
      return;
    }
    if (data.gotoNext && event.detail.reason === "next") {
      var newTarget = document.getElementById(data.gotoNext); // jshint ignore:line
      if (newTarget && newTarget.classList.contains("step")) {
        event.detail.next = newTarget;
        event.detail.transitionDuration = lib.util.toNumber(
          event.detail.next.dataset.transitionDuration,
          event.detail.transitionDuration
        );
        return;
      } else {
        window.console.log("impress goto plugin: " + data.gotoNext +
          " is not a step in this impress presentation.");
      }
    }

    // Handle event.target data-goto-prev 属性
    if (isNumber(data.gotoPrev) && event.detail.reason === "prev") {
      event.detail.next = steps[data.gotoPrev];
      event.detail.transitionDuration = lib.util.toNumber(
        event.detail.next.dataset.transitionDuration, event.detail.transitionDuration
      );
      return;
    }
    if (data.gotoPrev && event.detail.reason === "prev") {
      var newTarget = document.getElementById(data.gotoPrev); // jshint ignore:line
      if (newTarget && newTarget.classList.contains("step")) {
        event.detail.next = newTarget;
        event.detail.transitionDuration = lib.util.toNumber(
          event.detail.next.dataset.transitionDuration, event.detail.transitionDuration
        );
        return;
      } else {
        window.console.log("impress goto plugin: " + data.gotoPrev +
          " is not a step in this impress presentation.");
      }
    }

    // / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / 

    // Handle event.target data-goto 属性
    if (isNumber(data.goto)) {
      event.detail.next = steps[data.goto];
      event.detail.transitionDuration = lib.util.toNumber(
        event.detail.next.dataset.transitionDuration, event.detail.transitionDuration
      );
      return;
    }
    if (data.goto) {
      var newTarget = document.getElementById(data.goto); // jshint ignore:line
      if (newTarget && newTarget.classList.contains("step")) {
        event.detail.next = newTarget;
        event.detail.transitionDuration = lib.util.toNumber(
          event.detail.next.dataset.transitionDuration, event.detail.transitionDuration
        );
        return;
      } else {
        window.console.log("impress goto plugin: " + data.goto +
          " is not a step in this impress presentation.");
      }
    }
  };

  // 注册在预分步阶段调用的插件
  impress.addPreStepLeavePlugin(goto);

})(document, window);


/**
* Help popup plugin
*
* Example:
*
*     <!-- Show a help popup at start, or if user presses "H" -->
*     <div id="impress-help"></div>
*
* For developers:
*
* Typical use for this plugin, is for plugins that support some keypress, to add a line
* to the help popup produced by this plugin. For example "P: Presenter console".
*
* Copyright 2016 Henrik Ingo (@henrikingo)
* Released under the MIT license.
*/
/* global window, document */

(function (document, window) {
  "use strict";
  var rows = [];
  var timeoutHandle;

  var triggerEvent = function (el, eventName, detail) {
    var event = document.createEvent("CustomEvent");
    event.initCustomEvent(eventName, true, true, detail);
    el.dispatchEvent(event);
  };

  var renderHelpDiv = function () {
    var helpDiv = document.getElementById("impress-help");
    if (helpDiv) {
      var html = [];
      for (var row in rows) {
        for (var arrayItem in row) {
          html.push(rows[row][arrayItem]);
        }
      }
      if (html) {
        helpDiv.innerHTML = "<table>\n" + html.join("\n") + "</table>\n";
      }
    }
  };

  var toggleHelp = function () {
    var helpDiv = document.getElementById("impress-help");
    if (!helpDiv) {
      return;
    }

    if (helpDiv.style.display === "block") {
      helpDiv.style.display = "none";
    } else {
      helpDiv.style.display = "block";
      window.clearTimeout(timeoutHandle);
    }
  };

  document.addEventListener("keyup", function (event) {

    if (event.keyCode === 72 || event.keyCode === 191) { // "h" || "?"
      event.preventDefault();
      toggleHelp();
    }
  }, false);

  // 空气污染指数
  // 其他插件可以添加帮助文本,特别是当它们支持按键操作时。
  /**
   * Add a help text to the help popup.
   *
   * :param: e.detail.command  Example: "H"
   * :param: e.detail.text     Example: "Show this help."
   * :param: e.detail.row      Row index from 0 to 9 where to place this help text. Example: 0
   */
  document.addEventListener("impress:help:add", function (e) {

    // 这个想法是让事件的发送者提供一个唯一的行索引,用于排序。
    // 但是为了防止两个插件使用相同的行索引,我们将每一行都包装到
    // 它自己的数组。 如果同一索引有多个条目,则在先到先得。
    var rowIndex = e.detail.row;
    if (typeof rows[rowIndex] !== "object" || !rows[rowIndex].isArray) {
      rows[rowIndex] = [];
    }
    rows[e.detail.row].push("<tr><td><strong>" + e.detail.command + "</strong></td><td>" +
      e.detail.text + "</td></tr>");
    renderHelpDiv();
  });

  document.addEventListener("impress:init", function (e) {
    renderHelpDiv();

    // 开始时,显示帮助信息7秒钟。
    var helpDiv = document.getElementById("impress-help");
    if (helpDiv) {
      helpDiv.style.display = "block";
      timeoutHandle = window.setTimeout(function () {
        var helpDiv = document.getElementById("impress-help");
        helpDiv.style.display = "none";
      }, 7000);

      // Regster 回调以清空拆卸时的 help div
      var api = e.detail.api;
      api.lib.gc.pushCallback(function () {
        window.clearTimeout(timeoutHandle);
        helpDiv.style.display = "";
        helpDiv.innerHTML = "";
        rows = [];
      });
    }

    // 使用我们自己的 API 为“ h”注册帮助文本
    triggerEvent(document, "impress:help:add",
      { command: "H", text: "Show this help", row: 0 });
  });

})(document, window);


/**
* Adds a presenter console to impress.js
*
* MIT Licensed, see license.txt.
*
* Copyright 2012, 2013, 2015 impress-console contributors (see README.txt)
*
* version: 1.3-dev
*
*/

// 这个文件包含了太多的 HTML,我们只能恭敬地对 js 提出异议
/* jshint quotmark:single */
/* global navigator, top, setInterval, clearInterval, document, window */

(function (document, window) {
  'use strict';

  // TODO: 将其移动到 src / lib / util. js
  var triggerEvent = function (el, eventName, detail) {
    var event = document.createEvent('CustomEvent');
    event.initCustomEvent(eventName, true, true, detail);
    el.dispatchEvent(event);
  };

  // 根据浏览器语言设置创建 Language 对象
  var lang;
  switch (navigator.language) {
    case 'de':
      lang = {
        'noNotes': '<div class="noNotes">Keine Notizen hierzu</div>',
        'restart': 'Neustart',
        'clickToOpen': 'Klicken um Sprecherkonsole zu öffnen',
        'prev': 'zurück',
        'next': 'weiter',
        'loading': 'initalisiere',
        'ready': 'Bereit',
        'moving': 'in Bewegung',
        'useAMPM': false
      };
      break;
    case 'en': // jshint ignore:line
    default: // jshint ignore:line
      lang = {
        'noNotes': '<div class="noNotes">No notes for this step</div>',
        'restart': 'Restart',
        'clickToOpen': 'Click to open speaker console',
        'prev': 'Prev',
        'next': 'Next',
        'loading': 'Loading',
        'ready': 'Ready',
        'moving': 'Moving',
        'useAMPM': false
      };
      break;
  }

  // 在扬声器控制台中设置 iframe
  const preViewDefaultFactor = 0.7;
  const preViewMinimumFactor = 0.5;
  const preViewGap = 4;

  // 这是扬声器控制台窗口的默认模板
  const consoleTemplate = '<!DOCTYPE html>' +
    '<html id="impressconsole"><head>' +

    // Order 很重要: 如果用户提供了一个 cssFile,那么这些文件将获胜,因为它们比较晚
    '{{cssStyle}}' +
    '{{cssLink}}' +
    '</head><body>' +
    '<div id="console">' +
    '<div id="views">' +
    '<iframe id="slideView" scrolling="no"></iframe>' +
    '<iframe id="preView" scrolling="no"></iframe>' +
    '<div id="blocker"></div>' +
    '</div>' +
    '<div id="notes"></div>' +
    '</div>' +
    '<div id="controls"> ' +
    '<div id="prev"><a  href="#" onclick="impress().prev(); return false;" />' +
    '{{prev}}</a></div>' +
    '<div id="next"><a  href="#" onclick="impress().next(); return false;" />' +
    '{{next}}</a></div>' +
    '<div id="clock">--:--</div>' +
    '<div id="timer" onclick="timerReset()">00m 00s</div>' +
    '<div id="status">{{loading}}</div>' +
    '</div>' +
    '</body></html>';

  // 默认 css 位置
  var cssFileOldDefault = 'css/impressConsole.css';
  var cssFile = undefined; // jshint ignore:line

  // Css 用于在控制台上设置 iframs 样式
  var cssFileIframeOldDefault = 'css/iframe.css';
  var cssFileIframe = undefined; // jshint ignore:line

  // 所有控制台窗口,以便您可以重复调用 impressConsole ()。
  var allConsoles = {};

  // Zero padding helper 函数:
  var zeroPad = function (i) {
    return (i < 10 ? '0' : '') + i;
  };

  // 控制台对象
  var impressConsole = window.impressConsole = function (rootId) {

    rootId = rootId || 'impress';

    if (allConsoles[rootId]) {
      return allConsoles[rootId];
    }

    // 根表示元素
    var root = document.getElementById(rootId);

    var consoleWindow = null;

    var nextStep = function () {
      var classes = '';
      var nextElement = document.querySelector('.active');

      // 只要没有下一个兄弟姐妹,就回到父母身边
      while (!nextElement.nextElementSibling && nextElement.parentNode) {
        nextElement = nextElement.parentNode;
      }
      nextElement = nextElement.nextElementSibling;
      while (nextElement) {
        classes = nextElement.attributes['class'];
        if (classes && classes.value.indexOf('step') !== -1) {
          consoleWindow.document.getElementById('blocker').innerHTML = lang.next;
          return nextElement;
        }

        if (nextElement.firstElementChild) { // First go into deep
          nextElement = nextElement.firstElementChild;
        } else {

          // 转到下一个兄弟姐妹或通过父母,直到有下一个兄弟姐妹
          while (!nextElement.nextElementSibling && nextElement.parentNode) {
            nextElement = nextElement.parentNode;
          }
          nextElement = nextElement.nextElementSibling;
        }
      }

      // 没有下一个元素。选择第一个元素
      consoleWindow.document.getElementById('blocker').innerHTML = lang.restart;
      return document.querySelector('.step');
    };

    // 将笔记同步到步骤中
    var onStepLeave = function () {
      if (consoleWindow) {

        // 将注释设置为下一步注释。
        var newNotes = document.querySelector('.active').querySelector('.notes');
        if (newNotes) {
          newNotes = newNotes.innerHTML;
        } else {
          newNotes = lang.noNotes;
        }
        consoleWindow.document.getElementById('notes').innerHTML = newNotes;

        // 设置视图
        var baseURL = document.URL.substring(0, document.URL.search('#/'));
        var slideSrc = baseURL + '#' + document.querySelector('.active').id;
        var preSrc = baseURL + '#' + nextStep().id;
        var slideView = consoleWindow.document.getElementById('slideView');

        // 当它们已经设置好的时候设置它们会导致 Firefox 中的滑动,所以先检查一下:
        if (slideView.src !== slideSrc) {
          slideView.src = slideSrc;
        }
        var preView = consoleWindow.document.getElementById('preView');
        if (preView.src !== preSrc) {
          preView.src = preSrc;
        }

        consoleWindow.document.getElementById('status').innerHTML =
          '<span class="moving">' + lang.moving + '</span>';
      }
    };

    // 将预览同步到步骤
    var onStepEnter = function () {
      if (consoleWindow) {

        // 我们在这里做所有的事情,因为如果你停止 previos 步骤
        // 早些时候,onstepleave 触发器没有被调用,所以
        // 我们需要这个来同步。
        var newNotes = document.querySelector('.active').querySelector('.notes');
        if (newNotes) {
          newNotes = newNotes.innerHTML;
        } else {
          newNotes = lang.noNotes;
        }
        var notes = consoleWindow.document.getElementById('notes');
        notes.innerHTML = newNotes;
        notes.scrollTop = 0;

        // 设置视图
        var baseURL = document.URL.substring(0, document.URL.search('#/'));
        var slideSrc = baseURL + '#' + document.querySelector('.active').id;
        var preSrc = baseURL + '#' + nextStep().id;
        var slideView = consoleWindow.document.getElementById('slideView');

        // 当它们已经设置好的时候设置它们会导致 Firefox 中的滑动,所以先检查一下:
        if (slideView.src !== slideSrc) {
          slideView.src = slideSrc;
        }
        var preView = consoleWindow.document.getElementById('preView');
        if (preView.src !== preSrc) {
          preView.src = preSrc;
        }

        consoleWindow.document.getElementById('status').innerHTML =
          '<span  class="ready">' + lang.ready + '</span>';
      }
    };

    // Sync 子步骤
    var onSubstep = function (event) {
      if (consoleWindow) {
        if (event.detail.reason === 'next') {
          onSubstepShow();
        }
        if (event.detail.reason === 'prev') {
          onSubstepHide();
        }
      }
    };

    var onSubstepShow = function () {
      var slideView = consoleWindow.document.getElementById('slideView');
      triggerEventInView(slideView, 'impress:substep:show');
    };

    var onSubstepHide = function () {
      var slideView = consoleWindow.document.getElementById('slideView');
      triggerEventInView(slideView, 'impress:substep:hide');
    };

    var triggerEventInView = function (frame, eventName, detail) {

      // 注意: 不幸的是 Chrome 不允许在文件: / / url 上创建事件,所以它不会
      // 工作。 这在 Firefox 上是可行的,并且如果你在一个 Firefox 浏览器上浏览演示文稿的话也可以
      Http: // URL on Chrome.
      var event = frame.contentDocument.createEvent('CustomEvent');
      event.initCustomEvent(eventName, true, true, detail);
      frame.contentDocument.dispatchEvent(event);
    };

    var spaceHandler = function () {
      var notes = consoleWindow.document.getElementById('notes');
      if (notes.scrollTopMax - notes.scrollTop > 20) {
        notes.scrollTop = notes.scrollTop + notes.clientHeight * 0.8;
      } else {
        window.impress().next();
      }
    };

    var timerReset = function () {
      consoleWindow.timerStart = new Date();
    };

    // 展示一个时钟
    var clockTick = function () {
      var now = new Date();
      var hours = now.getHours();
      var minutes = now.getMinutes();
      var seconds = now.getSeconds();
      var ampm = '';

      if (lang.useAMPM) {
        ampm = (hours < 12) ? 'AM' : 'PM';
        hours = (hours > 12) ? hours - 12 : hours;
        hours = (hours === 0) ? 12 : hours;
      }

      // 时钟
      var clockStr = zeroPad(hours) + ':' + zeroPad(minutes) + ':' + zeroPad(seconds) +
        ' ' + ampm;
      consoleWindow.document.getElementById('clock').firstChild.nodeValue = clockStr;

      // 计时器
      seconds = Math.floor((now - consoleWindow.timerStart) / 1000);
      minutes = Math.floor(seconds / 60);
      seconds = Math.floor(seconds % 60);
      consoleWindow.document.getElementById('timer').firstChild.nodeValue =
        zeroPad(minutes) + 'm ' + zeroPad(seconds) + 's';

      if (!consoleWindow.initialized) {

        // 在载入之后轻推幻灯片窗口,否则它们将在 Firefox 上滚动出错。
        consoleWindow.document.getElementById('slideView').contentWindow.scrollTo(0, 0);
        consoleWindow.document.getElementById('preView').contentWindow.scrollTo(0, 0);
        consoleWindow.initialized = true;
      }
    };

    var registerKeyEvent = function (keyCodes, handler, window) {
      if (window === undefined) {
        window = consoleWindow;
      }

      // 当按下其中一个受支持的键时,防止默认的键下行操作
      window.document.addEventListener('keydown', function (event) {
        if (!event.ctrlKey && !event.altKey && !event.shiftKey && !event.metaKey &&
          keyCodes.indexOf(event.keyCode) !== -1) {
          event.preventDefault();
        }
      }, false);

      // 触发键起动动作
      window.document.addEventListener('keyup', function (event) {
        if (!event.ctrlKey && !event.altKey && !event.shiftKey && !event.metaKey &&
          keyCodes.indexOf(event.keyCode) !== -1) {
          handler();
          event.preventDefault();
        }
      }, false);
    };

    var consoleOnLoad = function () {
      var slideView = consoleWindow.document.getElementById('slideView');
      var preView = consoleWindow.document.getElementById('preView');

      // Firefox:
      slideView.contentDocument.body.classList.add('impress-console');
      preView.contentDocument.body.classList.add('impress-console');
      if (cssFileIframe !== undefined) {
        slideView.contentDocument.head.insertAdjacentHTML(
          'beforeend',
          '<link rel="stylesheet" type="text/css" href="' + cssFileIframe + '">'
        );
        preView.contentDocument.head.insertAdjacentHTML(
          'beforeend',
          '<link rel="stylesheet" type="text/css" href="' + cssFileIframe + '">'
        );
      }

      // Chrome:
      slideView.addEventListener('load', function () {
        slideView.contentDocument.body.classList.add('impress-console');
        if (cssFileIframe !== undefined) {
          slideView.contentDocument.head.insertAdjacentHTML(
            'beforeend',
            '<link rel="stylesheet" type="text/css" href="' +
            cssFileIframe + '">'
          );
        }
      });
      preView.addEventListener('load', function () {
        preView.contentDocument.body.classList.add('impress-console');
        if (cssFileIframe !== undefined) {
          preView.contentDocument.head.insertAdjacentHTML(
            'beforeend',
            '<link rel="stylesheet" type="text/css" href="' +
            cssFileIframe + '">');
        }
      });
    };

    var open = function () {
      if (top.isconsoleWindow) {
        return;
      }

      if (consoleWindow && !consoleWindow.closed) {
        consoleWindow.focus();
      } else {
        consoleWindow = window.open('', 'impressConsole');

        // 如果打开故障,这可能是因为浏览器阻止了
        // 不(或更少)交互式 JavaScript..。
        if (consoleWindow == null) {

          // ... 所以我在 klick 上加了一个按钮。
          // 在 firefox 上解决问题
          var message = document.createElement('div');
          message.id = 'impress-console-button';
          message.style.position = 'fixed';
          message.style.left = 0;
          message.style.top = 0;
          message.style.right = 0;
          message.style.bottom = 0;
          message.style.backgroundColor = 'rgba(255, 255, 255, 0.9)';
          var clickStr = 'var x = document.getElementById(\'impress-console-button\');' +
            'x.parentNode.removeChild(x);' +
            'var r = document.getElementById(\'' + rootId + '\');' +
            'impress(\'' + rootId +
            '\').lib.util.triggerEvent(r, \'impress:console:open\', {})';
          var styleStr = 'margin: 25vh 25vw;width:50vw;height:50vh;';
          message.innerHTML = '<button style="' + styleStr + '" ' +
            'onclick="' + clickStr + '">' +
            lang.clickToOpen +
            '</button>';
          document.body.appendChild(message);
          return;
        }

        var cssLink = '';
        if (cssFile !== undefined) {
          cssLink = '<link rel="stylesheet" type="text/css" media="screen" href="' +
            cssFile + '">';
        }

        // 这将窗口位置设置为主窗口位置,因此可以加载 css:
        consoleWindow.document.open();

        // 编写模板:
        consoleWindow.document.write(

          // CssStyleStr 是在该文件末尾定义的大量内联样式 / 样式
          consoleTemplate.replace('{{cssStyle}}', cssStyleStr())
            .replace('{{cssLink}}', cssLink)
            .replace(/{{.*?}}/gi, function (x) {
              return lang[x.substring(2, x.length - 2)];
            }
            )
        );
        consoleWindow.document.title = 'Speaker Console (' + document.title + ')';
        consoleWindow.impress = window.impress;

        // 我们设置这个标志,这样我们以后可以检测到它,以防止无限弹出窗口。
        consoleWindow.isconsoleWindow = true;

        // 设置 onload 函数:
        consoleWindow.onload = consoleOnLoad;

        // 添加时钟滴答声
        consoleWindow.timerStart = new Date();
        consoleWindow.timerReset = timerReset;
        consoleWindow.clockInterval = setInterval(allConsoles[rootId].clockTick, 1000);

        // 键盘导航处理程序
        // 33: pg up,37: 左,38: up
        registerKeyEvent([33, 37, 38], window.impress().prev);

        // 34: pg down,39: right,40: down
        registerKeyEvent([34, 39, 40], window.impress().next);

        // 32: space
        registerKeyEvent([32], spaceHandler);

        // 82: r
        registerKeyEvent([82], timerReset);

        // 清理
        consoleWindow.onbeforeunload = function () {

          // 我不知道为什么 onunload 在这里不起作用。
          clearInterval(consoleWindow.clockInterval);
        };

        // 在 Firefox 上需要一个小小的推动,但是只能在加载之后:
        onStepEnter();
        consoleWindow.initialized = false;
        consoleWindow.document.close();

        // 捕获任何调整窗口大小以传递大小
        window.onresize = resize;
        consoleWindow.onresize = resize;

        return consoleWindow;
      }
    };

    var resize = function () {
      var slideView = consoleWindow.document.getElementById('slideView');
      var preView = consoleWindow.document.getElementById('preView');

      // 得到展示的比例
      var ratio = window.innerHeight / window.innerWidth;

      // 获取视图可用的大小
      var views = consoleWindow.document.getElementById('views');

      // SlideView 可能有一个边框或者一些填充:
      // 两个窗口的边框宽度相同
      var delta = slideView.offsetWidth - slideView.clientWidth;

      // 设置视图
      var slideViewWidth = (views.clientWidth - delta);
      var slideViewHeight = Math.floor(slideViewWidth * ratio);

      var preViewTop = slideViewHeight + preViewGap;

      var preViewWidth = Math.floor(slideViewWidth * preViewDefaultFactor);
      var preViewHeight = Math.floor(slideViewHeight * preViewDefaultFactor);

      // 缩小预览,以适应可用的空间
      if (views.clientHeight - delta < preViewTop + preViewHeight) {
        preViewHeight = views.clientHeight - delta - preViewTop;
        preViewWidth = Math.floor(preViewHeight / ratio);
      }

      // 如果预览不够高,忘记比率!
      if (preViewWidth <= Math.floor(slideViewWidth * preViewMinimumFactor)) {
        slideViewWidth = (views.clientWidth - delta);
        slideViewHeight = Math.floor((views.clientHeight - delta - preViewGap) /
          (1 + preViewMinimumFactor));

        preViewTop = slideViewHeight + preViewGap;

        preViewWidth = Math.floor(slideViewWidth * preViewMinimumFactor);
        preViewHeight = views.clientHeight - delta - preViewTop;
      }

      // 将计算结果设置为样式
      slideView.style.width = slideViewWidth + 'px';
      slideView.style.height = slideViewHeight + 'px';

      preView.style.top = preViewTop + 'px';

      preView.style.width = preViewWidth + 'px';
      preView.style.height = preViewHeight + 'px';
    };

    var _init = function (cssConsole, cssIframe) {
      if (cssConsole !== undefined) {
        cssFile = cssConsole;
      }

      // 你也可以在 presentation root div 中指定 css:
      // div id“ impress” data-console-css... “ data-console-css-iframe” ..
      else if (root.dataset.consoleCss !== undefined) {
        cssFile = root.dataset.consoleCss;
      }

      if (cssIframe !== undefined) {
        cssFileIframe = cssIframe;
      } else if (root.dataset.consoleCssIframe !== undefined) {
        cssFileIframe = root.dataset.consoleCssIframe;
      }

      // 登记活动
      root.addEventListener('impress:stepleave', onStepLeave);
      root.addEventListener('impress:stepenter', onStepEnter);
      root.addEventListener('impress:substep:stepleaveaborted', onSubstep);
      root.addEventListener('impress:substep:show', onSubstepShow);
      root.addEventListener('impress:substep:hide', onSubstepHide);

      // 当窗户关上时,我们自己清理干净。
      window.onunload = function () {
        if (consoleWindow && !consoleWindow.closed) {
          consoleWindow.close();
        }
      };

      // 当他们按“ p”时打开扬声器控制台
      registerKeyEvent([80], open, window);

      // 顺便说一句,你也可以自动启动控制台:
      // div id"impress"data-console-autolalaunch"true
      if (root.dataset.consoleAutolaunch === 'true') {
        open();
      }
    };

    var init = function (cssConsole, cssIframe) {
      if ((cssConsole === undefined || cssConsole === cssFileOldDefault) &&
        (cssIframe === undefined || cssIframe === cssFileIframeOldDefault)) {
        window.console.log('impressConsole().init() is deprecated. ' +
          'impressConsole is now initialized automatically when you ' +
          'call impress().init().');
      }
      _init(cssConsole, cssIframe);
    };

    // impress.js 插件的新 API 是基于使用事件的
    root.addEventListener('impress:console:open', function () {
      open();
    });

    /**
     * Register a key code to an event handler
     *
     * :param: event.detail.keyCodes    List of key codes
     * :param: event.detail.handler     A function registered as the event handler
     * :param: event.detail.window      The console window to register the keycode in
     */
    root.addEventListener('impress:console:registerKeyEvent', function (event) {
      registerKeyEvent(event.detail.keyCodes, event.detail.handler, event.detail.window);
    });

    // 返回对象
    allConsoles[rootId] = {
      init: init, open: open, clockTick: clockTick,
      registerKeyEvent: registerKeyEvent, _init: _init
    };
    return allConsoles[rootId];

  };

  // 当初始化 impress 本身时,它会自动初始化 impressConsole
  document.addEventListener('impress:init', function (event) {

    // 注意: impressConsole 需要 id 字符串,而不是直接的 DOM 元素
    impressConsole(event.target.id)._init();

    // 在帮助弹出窗口中添加‘ p'
    triggerEvent(document, 'impress:help:add',
      { command: 'P', text: 'Presenter console', row: 10 });
  });

  // 返回一个字符串作为控制台窗口中的 css 样式元素内联使用。
  // 抱歉篇幅太长,但是把它隐藏在这里的末尾是为了让它远离代码的其他部分。
  var cssStyleStr = function () {
    return `<style>
          #impressconsole body {
              background-color: rgb(255, 255, 255);
              padding: 0;
              margin: 0;
              font-family: verdana, arial, sans-serif;
              font-size: 2vw;
          }
          #impressconsole div#console {
              position: absolute;
              top: 0.5vw;
              left: 0.5vw;
              right: 0.5vw;
              bottom: 3vw;
              margin: 0;
          }
          #impressconsole div#views, #impressconsole div#notes {
              position: absolute;
              top: 0;
              bottom: 0;
          }
          #impressconsole div#views {
              left: 0;
              right: 50vw;
              overflow: hidden;
          }
          #impressconsole div#blocker {
              position: absolute;
              right: 0;
              bottom: 0;
          }
          #impressconsole div#notes {
              left: 50vw;
              right: 0;
              overflow-x: hidden;
              overflow-y: auto;
              padding: 0.3ex;
              background-color: rgb(255, 255, 255);
              border: solid 1px rgb(120, 120, 120);
          }
          #impressconsole div#notes .noNotes {
              color: rgb(200, 200, 200);
          }
          #impressconsole div#notes p {
              margin-top: 0;
          }
          #impressconsole iframe {
              position: absolute;
              margin: 0;
              padding: 0;
              left: 0;
              border: solid 1px rgb(120, 120, 120);
          }
          #impressconsole iframe#slideView {
              top: 0;
              width: 49vw;
              height: 49vh;
          }
          #impressconsole iframe#preView {
              opacity: 0.7;
              top: 50vh;
              width: 30vw;
              height: 30vh;
          }
          #impressconsole div#controls {
              margin: 0;
              position: absolute;
              bottom: 0.25vw;
              left: 0.5vw;
              right: 0.5vw;
              height: 2.5vw;
              background-color: rgb(255, 255, 255);
              background-color: rgba(255, 255, 255, 0.6);
          }
          #impressconsole div#prev, div#next {
          }
          #impressconsole div#prev a, #impressconsole div#next a {
              display: block;
              border: solid 1px rgb(70, 70, 70);
              border-radius: 0.5vw;
              font-size: 1.5vw;
              padding: 0.25vw;
              text-decoration: none;
              background-color: rgb(220, 220, 220);
              color: rgb(0, 0, 0);
          }
          #impressconsole div#prev a:hover, #impressconsole div#next a:hover {
              background-color: rgb(245, 245, 245);
          }
          #impressconsole div#prev {
              float: left;
          }
          #impressconsole div#next {
              float: right;
          }
          #impressconsole div#status {
              margin-left: 2em;
              margin-right: 2em;
              text-align: center;
              float: right;
          }
          #impressconsole div#clock {
              margin-left: 2em;
              margin-right: 2em;
              text-align: center;
              float: left;
          }
          #impressconsole div#timer {
              margin-left: 2em;
              margin-right: 2em;
              text-align: center;
              float: left;
          }
          #impressconsole span.moving {
              color: rgb(255, 0, 0);
          }
          #impressconsole span.ready {
              color: rgb(0, 128, 0);
          }
      </style>`;
  };

})(document, window);

/* global window, document */

(function (document, window) {
  "use strict";
  
  var root, api, gc, attributeTracker;

  attributeTracker = [];

  // 函数名
  var enhanceMediaNodes,
    enhanceMedia,
    removeMediaClasses,
    onStepenterDetectImpressConsole,
    onStepenter,
    onStepleave,
    onPlay,
    onPause,
    onEnded,
    getMediaAttribute,
    teardown;

  document.addEventListener("impress:init", function (event) {
    root = event.target;
    api = event.detail.api;
    gc = api.lib.gc;

    enhanceMedia();

    gc.pushCallback(teardown);
  }, false);

  teardown = function () {
    var el, i;
    removeMediaClasses();
    for (i = 0; i < attributeTracker.length; i += 1) {
      el = attributeTracker[i];
      el.node.removeAttribute(el.attr);
    }
    attributeTracker = [];
  };

  getMediaAttribute = function (attributeName, nodes) {
    var attrName, attrValue, i, node;
    attrName = "data-media-" + attributeName;

    // 在所有节点中查找属性
    for (i = 0; i < nodes.length; i += 1) {
      node = nodes[i];

      // First test,如果该属性存在,因为某些浏览器可能会返回
      // 一个非现有属性的空字符串-specs 在这一点上并不清楚
      if (node.hasAttribute(attrName)) {

        找到 // 属性,返回解析后的布尔值,空字符串计数为 true
        // 以启用空值布尔值(常见于 html5,但不允许在格式良好的
        // xml).
        attrValue = node.getAttribute(attrName);
        if (attrValue === "" || attrValue === "true") {
          return true;
        } else {
          return false;
        }
      }

      // 在当前节点没有找到属性,继续下一轮
    }

    // 最后一招: no attribute found-return undefined to distiguish from false
    return undefined;
  };

  onPlay = function (event) {
    var type = event.target.nodeName.toLowerCase();
    document.body.classList.add("impress-media-" + type + "-playing");
    document.body.classList.remove("impress-media-" + type + "-paused");
  };

  onPause = function (event) {
    var type = event.target.nodeName.toLowerCase();
    document.body.classList.add("impress-media-" + type + "-paused");
    document.body.classList.remove("impress-media-" + type + "-playing");
  };

  onEnded = function (event) {
    var type = event.target.nodeName.toLowerCase();
    document.body.classList.remove("impress-media-" + type + "-playing");
    document.body.classList.remove("impress-media-" + type + "-paused");
  };

  removeMediaClasses = function () {
    var type, types;
    types = ["video", "audio"];
    for (type in types) {
      document.body.classList.remove("impress-media-" + types[type] + "-playing");
      document.body.classList.remove("impress-media-" + types[type] + "-paused");
    }
  };

  enhanceMediaNodes = function () {
    var i, id, media, mediaElement, type;

    media = root.querySelectorAll("audio, video");
    for (i = 0; i < media.length; i += 1) {
      type = media[i].nodeName.toLowerCase();

      // 设置一个 id 来标识每个媒体节点-例如,用于交叉引用
      // consoleMedia 插件
      mediaElement = media[i];
      id = mediaElement.getAttribute("id");
      if (id === undefined || id === null) {
        mediaElement.setAttribute("id", "media-" + type + "-" + i);
        attributeTracker.push({ "node": mediaElement, "attr": "id" });
      }
      gc.addEventListener(mediaElement, "play", onPlay);
      gc.addEventListener(mediaElement, "playing", onPlay);
      gc.addEventListener(mediaElement, "pause", onPause);
      gc.addEventListener(mediaElement, "ended", onEnded);
    }
  };

  enhanceMedia = function () {
    var steps, stepElement, i;
    enhanceMediaNodes();
    steps = document.getElementsByClassName("step");
    for (i = 0; i < steps.length; i += 1) {
      stepElement = steps[i];

      gc.addEventListener(stepElement, "impress:stepenter", onStepenter);
      gc.addEventListener(stepElement, "impress:stepleave", onStepleave);
    }
  };

  onStepenterDetectImpressConsole = function () {
    return {
      "preview": (window.frameElement !== null && window.frameElement.id === "preView"),
      "slideView": (window.frameElement !== null && window.frameElement.id === "slideView")
    };
  };

  onStepenter = function (event) {
    var stepElement, media, mediaElement, i, onConsole, autoplay;
    if ((!event) || (!event.target)) {
      return;
    }

    stepElement = event.target;
    removeMediaClasses();

    media = stepElement.querySelectorAll("audio, video");
    for (i = 0; i < media.length; i += 1) {
      mediaElement = media[i];

      // Autoplay 当(可能是继承的)自动播放设置为 true 时,
      // ,但仅限于在预览定额备用金控制台的下一步时
      onConsole = onStepenterDetectImpressConsole();
      autoplay = getMediaAttribute("autoplay", [mediaElement, stepElement, root]);
      if (autoplay && !onConsole.preview) {
        if (onConsole.slideView) {
          mediaElement.muted = true;
        }
        mediaElement.play();
      }
    }
  };

  onStepleave = function (event) {
    var stepElement, media, i, mediaElement, autoplay, autopause, autostop;
    if ((!event || !event.target)) {
      return;
    }

    stepElement = event.target;
    media = event.target.querySelectorAll("audio, video");
    for (i = 0; i < media.length; i += 1) {
      mediaElement = media[i];

      autoplay = getMediaAttribute("autoplay", [mediaElement, stepElement, root]);
      autopause = getMediaAttribute("autopause", [mediaElement, stepElement, root]);
      autostop = getMediaAttribute("autostop", [mediaElement, stepElement, root]);

      // 如果自动停止和自动停止都未定义,请将其设置为自动停止的值
      if (autostop === undefined && autopause === undefined) {
        autostop = autoplay;
      }

      if (autopause || autostop) {
        mediaElement.pause();
        if (autostop) {
          mediaElement.currentTime = 0;
        }
      }
    }
    removeMediaClasses();
  };

})(document, window);

/**
* Mobile devices support
*
* Allow presentation creators to hide all but 3 slides, to save resources, particularly on mobile
* devices, using classes body.impress-mobile, .step.prev, .step.active and .step.next.
*
* Note: This plugin does not take into account possible redirections done with skip, goto etc
* plugins. Basically it wouldn't work as intended in such cases, but the active step will at least
* be correct.
*
* Adapted to a plugin from a submission by @Kzeni:
* https://github.com/impress/impress.js/issues/333
*/
/* global document, navigator */
(function (document) {
  "use strict";

  var getNextStep = function (el) {
    var steps = document.querySelectorAll(".step");
    for (var i = 0; i < steps.length; i++) {
      if (steps[i] === el) {
        if (i + 1 < steps.length) {
          return steps[i + 1];
        } else {
          return steps[0];
        }
      }
    }
  };
  var getPrevStep = function (el) {
    var steps = document.querySelectorAll(".step");
    for (var i = steps.length - 1; i >= 0; i--) {
      if (steps[i] === el) {
        if (i - 1 >= 0) {
          return steps[i - 1];
        } else {
          return steps[steps.length - 1];
        }
      }
    }
  };

  // 检测移动浏览器并酌情添加 CSS 类。
  document.addEventListener("impress:init", function (event) {
    var body = document.body;
    if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
      navigator.userAgent
    )) {
      body.classList.add("impress-mobile");
    }

    // 把这些都拆掉
    var api = event.detail.api;
    api.lib.gc.pushCallback(function () {
      document.body.classList.remove("impress-mobile");
      var prev = document.getElementsByClassName("prev")[0];
      var next = document.getElementsByClassName("next")[0];
      if (typeof prev !== "undefined") {
        prev.classList.remove("prev");
      }
      if (typeof next !== "undefined") {
        next.classList.remove("next");
      }
    });
  });

  // 将 prev 和 next 类添加到新输入的活动 step 元素的兄弟类
  // 从当前步骤元素中删除 prev 和 next 类
  // 注意: 作为一个例外,我们打破了名称空间规则,因为这些都是有用的通用规则
  // 课程。 (命名规则要求我们使用 css 类 mobile-next 和 mobile-prev,
  // 基于插件名。)
  document.addEventListener("impress:stepenter", function (event) {
    var oldprev = document.getElementsByClassName("prev")[0];
    var oldnext = document.getElementsByClassName("next")[0];

    var prev = getPrevStep(event.target);
    prev.classList.add("prev");
    var next = getNextStep(event.target);
    next.classList.add("next");

    if (typeof oldprev !== "undefined") {
      oldprev.classList.remove("prev");
    }
    if (typeof oldnext !== "undefined") {
      oldnext.classList.remove("next");
    }
  });
})(document);


/**
* Mouse timeout plugin
*
* After 3 seconds of mouse inactivity, add the css class
* 'body.impress-mouse-timeout'. On 'mousemove', 'click' or 'touch', remove the
* class.
*
* The use case for this plugin is to use CSS to hide elements from the screen
* and only make them visible when the mouse is moved. Examples where this
* might be used are: the toolbar from the toolbar plugin, and the mouse cursor
* itself.
*
* Example CSS:
*
*     body.impress-mouse-timeout {
*         cursor: none;
*     }
*     body.impress-mouse-timeout div#impress-toolbar {
*         display: none;
*     }
*
*
* Copyright 2016 Henrik Ingo (@henrikingo)
* Released under the MIT license.
*/
/* global window, document */
(function (document, window) {
  "use strict";
  var timeout = 3;
  var timeoutHandle;

  var hide = function () {

    // 鼠标现在处于非活动状态
    document.body.classList.add("impress-mouse-timeout");
  };

  var show = function () {
    if (timeoutHandle) {
      window.clearTimeout(timeoutHandle);
    }

    // 鼠标现在是活动的
    document.body.classList.remove("impress-mouse-timeout");

    // 然后设置新的超时,超时之后将再次被视为不活动
    timeoutHandle = window.setTimeout(hide, timeout * 1000);
  };

  document.addEventListener("impress:init", function (event) {
    var api = event.detail.api;
    var gc = api.lib.gc;
    gc.addEventListener(document, "mousemove", show);
    gc.addEventListener(document, "click", show);
    gc.addEventListener(document, "touch", show);

    // 设置第一个超时
    show();

    // 把这些都拆掉
    gc.pushCallback(function () {
      window.clearTimeout(timeoutHandle);
      document.body.classList.remove("impress-mouse-timeout");
    });
  }, false);

})(document, window);

/**
* Navigation events plugin
*
* As you can see this part is separate from the impress.js core code.
* It's because these navigation actions only need what impress.js provides with
* its simple API.
*
* This plugin is what we call an _init plugin_. It's a simple kind of
* impress.js plugin. When loaded, it starts listening to the 'impress:init'
* event. That event listener initializes the plugin functionality - in this
* case we listen to some keypress and mouse events. The only dependencies on
* core impress.js functionality is the 'impress:init' method, as well as using
* the public api 'next(), prev(),' etc when keys are pressed.
*
* Copyright 2011-2012 Bartek Szopka (@bartaz)
* Released under the MIT license.
* ------------------------------------------------
*  author:  Bartek Szopka
*  version: 0.5.3
*  url:     http://bartaz.github.com/impress.js/
*  source:  http://github.com/bartaz/impress.js/
*
*/
/* global document */
(function (document) {
  "use strict";

  // 等待 impress.js 初始化
  document.addEventListener("impress:init", function (event) {

    // 从事件数据获取 API。
    // 所以你不需要知道根元素的 id 是什么
    // 或任何东西。‘ impress: init'event data gives you everything you
    // 需要控制刚刚初始化的表示。
    var api = event.detail.api;
    var gc = api.lib.gc;
    var util = api.lib.util;

    // 支持的键是:
    // [空间]-在演示软件中非常常见的前进方式
    // [上][右] / [下][左]-再次普通而自然的加法,
    // [ pgdown ] / [ pgup ]-通常由遥控器触发,
    // [ tab ]-这是一个相当有争议的问题,但是它最终出现在
    // 这个列表是一个相当有趣的故事... 还记得那个奇怪的部分吗
    // 在 impress.js 代码中,每次演示时窗口滚动到0,0
    // step,因为有时浏览器会因为聚焦元素而滚动视图?
    // 在默认情况下,[ tab ]键会在可关注元素周围导航,所以单击
    // 它经常导致滚动到聚焦元素并破坏 impress.js
    // 定位。 我不想仅仅阻止这个默认操作,所以我使用了[ tab ]
    // 作为迈向下一步的另一种方式... 是的,我知道这是为了
    // 一致性我应该加上[ shift + tab ]作为相反的动作..。
    var isNavigationEvent = function (event) {

      // 不要触发导航,例如当用户使用 alt + tab 返回浏览器窗口时
      if (event.altKey || event.ctrlKey || event.metaKey) {
        return false;
      }

      // 对于 TAB,我们总是强制步骤导航,覆盖浏览器
      // 在输入元素、按钮和链接之间导航。
      if (event.keyCode === 9) {
        return true;
      }

      // 除了 TAB 以外,如果 shift 是 down,我们也会忽略按下的键。
      if (event.shiftKey) {
        return false;
      }

      if ((event.keyCode >= 32 && event.keyCode <= 34) ||
        (event.keyCode >= 37 && event.keyCode <= 40)) {
        return true;
      }
    };

    // 键盘导航处理程序

    // 当按下其中一个受支持的键时,防止默认的键下行操作。
    gc.addEventListener(document, "keydown", function (event) {
      if (isNavigationEvent(event)) {
        event.preventDefault();
      }
    }, false);

    // 触发按键上的压印动作(下一个或前一个)。
    gc.addEventListener(document, "keyup", function (event) {
      if (isNavigationEvent(event)) {
        if (event.shiftKey) {
          switch (event.keyCode) {
            case 9: // Shift+tab
              api.prev();
              break;
          }
        } else {
          switch (event.keyCode) {
            case 33: // Pg up
            case 37: // Left
            case 38: // Up
              api.prev(event);
              break;
            case 9:  // Tab
            case 32: // Space
            case 34: // Pg down
            case 39: // Right
            case 40: // Down
              api.next(event);
              break;
          }
        }
        event.preventDefault();
      }
    }, false);

    // 委派处理程序,以点击指向演示步骤的链接
    gc.addEventListener(document, "click", function (event) {

      // 带有“冒泡”的事件代表团
      // 检查事件目标(或其父目标中的任何一个是链接)
      var target = event.target;
      try {
        while ((target.tagName !== "A") &&
          (target !== document.documentElement)) {
          target = target.parentNode;
        }

        if (target.tagName === "A") {
          var href = target.getAttribute("href");

          // 如果它是指向表示步骤的链接,则将该步骤作为目标
          if (href && href[0] === "#") {
            target = document.getElementById(href.slice(1));
          }
        }

        if (api.goto(target)) {
          event.stopImmediatePropagation();
          event.preventDefault();
        }
      }
      catch (err) {

        // 例如,当点击按钮启动扬声器控制台时,按钮
        // 立即从 DOM 中删除
        // 我们得到了它,但是如果你真的想用它做任何事情,那么它就是空的。
        if (err instanceof TypeError &&
          err.message === "target is null") {
          return;
        }
        throw err;
      }
    }, false);

    // 用于单击步骤元素的委托处理程序
    gc.addEventListener(document, "click", function (event) {
      var target = event.target;
      try {

        // 查找非活动的最近的步骤元素
        while (!(target.classList.contains("step") &&
          !target.classList.contains("active")) &&
          (target !== document.documentElement)) {
          target = target.parentNode;
        }

        if (api.goto(target)) {
          event.preventDefault();
        }
      }
      catch (err) {

        // 例如,当点击按钮启动扬声器控制台时,按钮
        // 立即从 DOM 中删除
        // 我们得到了它,但是如果你真的想用它做任何事情,那么它就是空的。
        if (err instanceof TypeError &&
          err.message === "target is null") {
          return;
        }
        throw err;
      }
    }, false);

    // 在帮助弹出窗口中添加一行
    util.triggerEvent(document, "impress:help:add", {
      command: "Left &amp; Right",
      text: "Previous &amp; Next step",
      row: 1
    });

  }, false);

})(document);


/**
* Navigation UI plugin
*
* This plugin provides UI elements "back", "forward" and a list to select
* a specific slide number.
*
* The navigation controls are added to the toolbar plugin via DOM events. User must enable the
* toolbar in a presentation to have them visible.
*
* Copyright 2016 Henrik Ingo (@henrikingo)
* Released under the MIT license.
*/

// 这个文件包含了太多的 HTML,我们只能恭敬地对 js 提出异议
/* jshint quotmark:single */
/* global document */

(function (document) {
  'use strict';
  var toolbar;
  var api;
  var root;
  var steps;
  var hideSteps = [];
  var prev;
  var select;
  var next;

  var triggerEvent = function (el, eventName, detail) {
    var event = document.createEvent('CustomEvent');
    event.initCustomEvent(eventName, true, true, detail);
    el.dispatchEvent(event);
  };

  var makeDomElement = function (html) {
    var tempDiv = document.createElement('div');
    tempDiv.innerHTML = html;
    return tempDiv.firstChild;
  };

  var selectOptionsHtml = function () {
    var options = '';
    for (var i = 0; i < steps.length; i++) {

      // 省略在选择小部件中列为 hidden 的步骤
      if (hideSteps.indexOf(steps[i]) < 0) {
        options = options + '<option value="' + steps[i].id + '">' + // jshint ignore:line
          steps[i].id + '</option>' + '\n'; // jshint ignore:line
      }
    }
    return options;
  };

  var addNavigationControls = function (event) {
    api = event.detail.api;
    var gc = api.lib.gc;
    root = event.target;
    steps = root.querySelectorAll('.step');

    var prevHtml = '<button id="impress-navigation-ui-prev" title="Previous" ' +
      'class="impress-navigation-ui">&lt;</button>';
    var selectHtml = '<select id="impress-navigation-ui-select" title="Go to" ' +
      'class="impress-navigation-ui">' + '\n' +
      selectOptionsHtml() +
      '</select>';
    var nextHtml = '<button id="impress-navigation-ui-next" title="Next" ' +
      'class="impress-navigation-ui">&gt;</button>';

    prev = makeDomElement(prevHtml);
    prev.addEventListener('click',
      function () {
        api.prev();
      });
    select = makeDomElement(selectHtml);
    select.addEventListener('change',
      function (event) {
        api.goto(event.target.value);
      });
    gc.addEventListener(root, 'impress:steprefresh', function (event) {

      // As impresss.js core 现在允许动态编辑步骤,包括添加
      // 删除和重新排序步骤时,我们需要对选择列表进行重新排序
      // 每一个进步的事件。
      steps = root.querySelectorAll('.step');
      select.innerHTML = '\n' + selectOptionsHtml();

      // 确保列表总是显示我们实际上正在进行的步骤,即使它不是
      // 从列表中选择
      select.value = event.target.id;
    });
    next = makeDomElement(nextHtml);
    next.addEventListener('click',
      function () {
        api.next();
      });

    triggerEvent(toolbar, 'impress:toolbar:appendChild', { group: 0, element: prev });
    triggerEvent(toolbar, 'impress:toolbar:appendChild', { group: 0, element: select });
    triggerEvent(toolbar, 'impress:toolbar:appendChild', { group: 0, element: next });

  };

  // API,因为没有在选择小部件中列出给定的步骤。
  // 例如,如果在某个元素上设置类“ skip” ,则可能不希望它出现在
  // 名单。 Otoh 我们不能假设,或其他任何东西,所以步骤,用户希望省略
  // 必须与这个 API 调用一起专门添加。
  document.addEventListener('impress:navigation-ui:hideStep', function (event) {
    hideSteps.push(event.target);
    if (select) {
      select.innerHTML = selectOptionsHtml();
    }
  }, false);

  // 等待 impress.js 初始化
  document.addEventListener('impress:init', function (event) {
    toolbar = document.querySelector('#impress-toolbar');
    if (toolbar) {
      addNavigationControls(event);
    }
  }, false);

})(document);


/* global document */
(function (document) {
  "use strict";
  var root;
  var stepids = [];

  // 从 impress root 下的步骤获取 stepid
  var getSteps = function () {
    stepids = [];
    var steps = root.querySelectorAll(".step");
    for (var i = 0; i < steps.length; i++) {
      stepids[i + 1] = steps[i].id;
    }
  };

  // 等待 impress.js 初始化
  document.addEventListener("impress:init", function (event) {
    root = event.target;
    getSteps();
    var gc = event.detail.api.lib.gc;
    gc.pushCallback(function () {
      stepids = [];
      if (progressbar) {
        progressbar.style.width = "";
      }
      if (progress) {
        progress.innerHTML = "";
      }
    });
  });

  var progressbar = document.querySelector("div.impress-progressbar div");
  var progress = document.querySelector("div.impress-progress");

  if (null !== progressbar || null !== progress) {
    document.addEventListener("impress:stepleave", function (event) {
      updateProgressbar(event.detail.next.id);
    });

    document.addEventListener("impress:steprefresh", function (event) {
      getSteps();
      updateProgressbar(event.target.id);
    });

  }

  function updateProgressbar(slideId) {
    var slideNumber = stepids.indexOf(slideId);
    if (null !== progressbar) {
      var width = 100 / (stepids.length - 1) * (slideNumber);
      progressbar.style.width = width.toFixed(2) + "%";
    }
    if (null !== progress) {
      progress.innerHTML = slideNumber + "/" + (stepids.length - 1);
    }
  }
})(document);

/**
* Relative Positioning Plugin
*
* This plugin provides support for defining the coordinates of a step relative
* to the previous step. This is often more convenient when creating presentations,
* since as you add, remove or move steps, you may not need to edit the positions
* as much as is the case with the absolute coordinates supported by impress.js
* core.
*
* Example:
*
*         <!-- Position step 1000 px to the right and 500 px up from the previous step. -->
*         <div class="step" data-rel-x="1000" data-rel-y="500">
*
* Following html attributes are supported for step elements:
*
*     data-rel-x
*     data-rel-y
*     data-rel-z
*
* These values are also inherited from the previous step. This makes it easy to
* create a boring presentation where each slide shifts for example 1000px down
* from the previous.
*
* In addition to plain numbers, which are pixel values, it is also possible to
* define relative positions as a multiple of screen height and width, using
* a unit of "h" and "w", respectively, appended to the number.
*
* Example:
*
*        <div class="step" data-rel-x="1.5w" data-rel-y="1.5h">
*
* This plugin is a *pre-init plugin*. It is called synchronously from impress.js
* core at the beginning of 'impress().init()'. This allows it to process its own
* data attributes first, and possibly alter the data-x, data-y and data-z attributes
* that will then be processed by 'impress().init()'.
*
* (Another name for this kind of plugin might be called a *filter plugin*, but
* *pre-init plugin* is more generic, as a plugin might do whatever it wants in
* the pre-init stage.)
*
* Copyright 2016 Henrik Ingo (@henrikingo)
* Released under the MIT license.
*/

/* global document, window */

(function (document, window) {
  "use strict";

  var startingState = {};

  /**
   * Copied from core impress.js. We currently lack a library mechanism to
   * to share utility functions like this.
   */
  var toNumber = function (numeric, fallback) {
    return isNaN(numeric) ? (fallback || 0) : Number(numeric);
  };

  /**
   * Extends toNumber() to correctly compute also relative-to-screen-size values 5w and 5h.
   *
   * Returns the computed value in pixels with w/h postfix removed.
   */
  var toNumberAdvanced = function (numeric, fallback) {
    if (typeof numeric !== "string") {
      return toNumber(numeric, fallback);
    }
    var ratio = numeric.match(/^([+-]*[\d\.]+)([wh])$/);
    if (ratio == null) {
      return toNumber(numeric, fallback);
    } else {
      var value = parseFloat(ratio[1]);
      var multiplier = ratio[2] === "w" ? window.innerWidth : window.innerHeight;
      return value * multiplier;
    }
  };

  var computeRelativePositions = function (el, prev) {
    var data = el.dataset;

    if (!prev) {

      // 对于第一步,继承这些默认值
      prev = { x: 0, y: 0, z: 0, relative: { x: 0, y: 0, z: 0 } };
    }

    if (data.relTo) {

      var ref = document.getElementById(data.relTo);
      if (ref) {

        // 测试,如果上一步已经分配了一些位置数据
        if (el.compareDocumentPosition(ref) & Node.DOCUMENT_POSITION_PRECEDING) {
          prev.x = toNumber(ref.getAttribute("data-x"));
          prev.y = toNumber(ref.getAttribute("data-y"));
          prev.z = toNumber(ref.getAttribute("data-z"));
          prev.relative = {};
        } else {
          window.console.error(
            "impress.js rel plugin: Step \"" + data.relTo + "\" is not defined " +
            "*before* the current step. Referencing is limited to previously defined " +
            "steps. Please check your markup. Ignoring data-rel-to attribute of " +
            "this step. Have a look at the documentation for how to create relative " +
            "positioning to later shown steps with the help of the goto plugin."
          );
        }
      } else {

        // 找不到步骤
        window.console.warn(
          "impress.js rel plugin: \"" + data.relTo + "\" is not a valid step in this " +
          "impress.js presentation. Please check your markup. Ignoring data-rel-to " +
          "attribute of this step."
        );
      }
    }

    var step = {
      x: toNumber(data.x, prev.x),
      y: toNumber(data.y, prev.y),
      z: toNumber(data.z, prev.z),
      relative: {
        x: toNumberAdvanced(data.relX, prev.relative.x),
        y: toNumberAdvanced(data.relY, prev.relative.y),
        z: toNumberAdvanced(data.relZ, prev.relative.z)
      }
    };

    // 如果给定绝对值,则相对位置被忽略 / 零。
    // 注意,这也有重置任何继承的相对值的效果。
    if (data.x !== undefined) {
      step.relative.x = 0;
    }
    if (data.y !== undefined) {
      step.relative.y = 0;
    }
    if (data.z !== undefined) {
      step.relative.z = 0;
    }

    // 将相对位置应用于绝对位置,如果非零
    // 注意,在这一点上,相对值包含一个像素数值。
    step.x = step.x + step.relative.x;
    step.y = step.y + step.relative.y;
    step.z = step.z + step.relative.z;

    return step;
  };

  var rel = function (root) {
    var steps = root.querySelectorAll(".step");
    var prev;
    startingState[root.id] = [];
    for (var i = 0; i < steps.length; i++) {
      var el = steps[i];
      startingState[root.id].push({
        el: el,
        x: el.getAttribute("data-x"),
        y: el.getAttribute("data-y"),
        z: el.getAttribute("data-z"),
        relX: el.getAttribute("data-rel-x"),
        relY: el.getAttribute("data-rel-y"),
        relZ: el.getAttribute("data-rel-z")
      });
      var step = computeRelativePositions(el, prev);

      // 应用相对位置(如非零)
      el.setAttribute("data-x", step.x);
      el.setAttribute("data-y", step.y);
      el.setAttribute("data-z", step.z);
      prev = step;
    }
  };

  // 注册要在预 init 阶段调用的插件
  window.impress.addPreInitPlugin(rel);

  // 注册拆卸回调以重置 data.x,。 是的。 Z 值。
  document.addEventListener("impress:init", function (event) {
    var root = event.target;
    event.detail.api.lib.gc.pushCallback(function () {
      var steps = startingState[root.id];
      var step;
      while (step = steps.pop()) {
        // 在这个插件已经改变它的情况下重置 x / y / z。
        if (step.relX !== null) {
          if (step.x === null) {
            step.el.removeAttribute("data-x");
          } else {
            step.el.setAttribute("data-x", step.x);
          }
        }
        if (step.relY !== null) {
          if (step.y === null) {
            step.el.removeAttribute("data-y");
          } else {
            step.el.setAttribute("data-y", step.y);
          }
        }
        if (step.relZ !== null) {
          if (step.z === null) {
            step.el.removeAttribute("data-z");
          } else {
            step.el.setAttribute("data-z", step.z);
          }
        }
      }
      delete startingState[root.id];
    });
  }, false);
})(document, window);


/**
* Resize plugin
*
* Rescale the presentation after a window resize.
*
* Copyright 2011-2012 Bartek Szopka (@bartaz)
* Released under the MIT license.
* ------------------------------------------------
*  author:  Bartek Szopka
*  version: 0.5.3
*  url:     http://bartaz.github.com/impress.js/
*  source:  http://github.com/bartaz/impress.js/
*
*/

/* global document, window */

(function (document, window) {
  "use strict";

  // 等待 impress.js 初始化
  document.addEventListener("impress:init", function (event) {
    var api = event.detail.api;

    // 调整窗口大小时的重新排列显示
    api.lib.gc.addEventListener(window, "resize", api.lib.util.throttle(function () {

      // 强制再次激活步骤,以触发重新标记
      api.goto(document.querySelector(".step.active"), 500);
    }, 250), false);
  }, false);

})(document, window);


/**
* Skip Plugin
*
* Example:
*
*    <!-- This slide is disabled in presentations, when moving with next()
*         and prev() commands, but you can still move directly to it, for
*         example with a url (anything using goto()). -->
*         <div class="step skip">
*
* Copyright 2016 Henrik Ingo (@henrikingo)
* Released under the MIT license.
*/

/* global document, window */

(function (document, window) {
  "use strict";
  var util;

  document.addEventListener("impress:init", function (event) {
    util = event.detail.api.lib.util;
  }, false);

  var getNextStep = function (el) {
    var steps = document.querySelectorAll(".step");
    for (var i = 0; i < steps.length; i++) {
      if (steps[i] === el) {
        if (i + 1 < steps.length) {
          return steps[i + 1];
        } else {
          return steps[0];
        }
      }
    }
  };
  var getPrevStep = function (el) {
    var steps = document.querySelectorAll(".step");
    for (var i = steps.length - 1; i >= 0; i--) {
      if (steps[i] === el) {
        if (i - 1 >= 0) {
          return steps[i - 1];
        } else {
          return steps[steps.length - 1];
        }
      }
    }
  };

  var skip = function (event) {
    if ((!event) || (!event.target)) {
      return;
    }

    if (event.detail.next.classList.contains("skip")) {
      if (event.detail.reason === "next") {

        // 转到下一步
        event.detail.next = getNextStep(event.detail.next);

        // 再次调用这个插件,直到有一个步骤不能跳过
        skip(event);
      } else if (event.detail.reason === "prev") {

        // 转到上一步
        event.detail.next = getPrevStep(event.detail.next);
        skip(event);
      }

      // 如果新的下一个元素有它自己的 transitionDuration,我们负责设置
      // 在活动中也是如此
      event.detail.transitionDuration = util.toNumber(
        event.detail.next.dataset.transitionDuration, event.detail.transitionDuration
      );
    }
  };

  // 注册在预分步阶段调用的插件
  // 权重使这个插件提前运行。 这是一件好事,因为这个插件调用
  // 本身递归地。
  window.impress.addPreStepLeavePlugin(skip, 1);

})(document, window);


/**
* Stop Plugin
*
* Example:
*
*        <!-- Stop at this slide.
*             (For example, when used on the last slide, this prevents the
*             presentation from wrapping back to the beginning.) -->
*        <div class="step stop">
*
* Copyright 2016 Henrik Ingo (@henrikingo)
* Released under the MIT license.
*/
/* global document, window */
(function (document, window) {
  "use strict";

  var stop = function (event) {
    if ((!event) || (!event.target)) {
      return;
    }

    if (event.target.classList.contains("stop")) {
      if (event.detail.reason === "next") {
        return false;
      }
    }
  };

  // 注册在预分步阶段调用的插件
  // 权重使得这个插件运行得相当早。
  window.impress.addPreStepLeavePlugin(stop, 2);

})(document, window);


/**
* Substep Plugin
*
* Copyright 2017 Henrik Ingo (@henrikingo)
* Released under the MIT license.
*/

/* global document, window */

(function (document, window) {
  "use strict";

  // 从核心备用金 js 复制。 很适合转移到 src / lib / util。 Js.
  var triggerEvent = function (el, eventName, detail) {
    var event = document.createEvent("CustomEvent");
    event.initCustomEvent(eventName, true, true, detail);
    el.dispatchEvent(event);
  };

  var activeStep = null;
  document.addEventListener("impress:stepenter", function (event) {
    activeStep = event.target;
  }, false);

  var substep = function (event) {
    if ((!event) || (!event.target)) {
      return;
    }

    var step = event.target;
    var el; // Needed by jshint
    if (event.detail.reason === "next") {
      el = showSubstepIfAny(step);
      if (el) {

        // 向其他人发送一条消息,告诉他们我们中止了一个安全事件。
        triggerEvent(step, "impress:substep:stepleaveaborted",
          { reason: "next", substep: el });

        // Autoplay 使用它来重新加载自己
        triggerEvent(step, "impress:substep:enter",
          { reason: "next", substep: el });

        // 返回错误中止 stepleave 事件
        return false;
      }
    }
    if (event.detail.reason === "prev") {
      el = hideSubstepIfAny(step);
      if (el) {
        triggerEvent(step, "impress:substep:stepleaveaborted",
          { reason: "prev", substep: el });

        triggerEvent(step, "impress:substep:leave",
          { reason: "prev", substep: el });

        return false;
      }
    }
  };

  var showSubstepIfAny = function (step) {
    var substeps = step.querySelectorAll(".substep");
    var visible = step.querySelectorAll(".substep-visible");
    if (substeps.length > 0) {
      return showSubstep(substeps, visible);
    }
  };

  var showSubstep = function (substeps, visible) {
    if (visible.length < substeps.length) {
      var el = substeps[visible.length];
      el.classList.add("substep-visible");
      return el;
    }
  };

  var hideSubstepIfAny = function (step) {
    var substeps = step.querySelectorAll(".substep");
    var visible = step.querySelectorAll(".substep-visible");
    if (substeps.length > 0) {
      return hideSubstep(visible);
    }
  };

  var hideSubstep = function (visible) {
    if (visible.length > 0) {
      var el = visible[visible.length - 1];
      el.classList.remove("substep-visible");
      return el;
    }
  };

  // 注册在预分步阶段调用的插件。
  // 权重使该插件在其他 preStepLeave 插件之前运行。
  window.impress.addPreStepLeavePlugin(substep, 1);

  // 当输入一个步骤时,尤其是重新输入时,确保所有子步骤都是隐藏的
  // 起初
  document.addEventListener("impress:stepenter", function (event) {
    var step = event.target;
    var visible = step.querySelectorAll(".substep-visible");
    for (var i = 0; i < visible.length; i++) {
      visible[i].classList.remove("substep-visible");
    }
  }, false);

  // API 供其他人显示 / 隐藏下一个子步骤 / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / 下一个子步骤 / / / / / / / / / / / / / / / / / / / / / / / / / / / / 
  document.addEventListener("impress:substep:show", function () {
    showSubstepIfAny(activeStep);
  }, false);

  document.addEventListener("impress:substep:hide", function () {
    hideSubstepIfAny(activeStep);
  }, false);

})(document, window);

/**
* 支持在触摸设备上滑动和点击
*
* 此插件为插件设备实现导航,通过左 / 右滑动,
* 或在屏幕左 / 右边缘轻敲。
*
*
*
* 版权2015: Andrew Dunai (@and3rson)
* 修改为一个插件,2016: Henrik Ingo (@henrikingo)
*
* MIT License
*/

/ * global document,window * /
(function (document, window) {
  "use strict";

  // 触摸处理程序根据窗口大小检测左右滑动。
  // 如果 x 变化的差值大于屏幕宽度的1 / 20,
  // 我们只需调用一个适当的 API 函数来完成转换。
  var startX = 0;
  var lastX = 0;
  var lastDX = 0;
  var threshold = window.innerWidth / 20;

  document.addEventListener("touchstart", function (event) {
    lastX = startX = event.touches[0].clientX;
  });

  document.addEventListener("touchmove", function (event) {
    var x = event.touches[0].clientX;
    var diff = x - startX;

    // 用于触摸
    lastDX = lastX - x;
    lastX = x;

    window.impress().swipe(diff / window.innerWidth);
  });

  document.addEventListener("touchend", function () {
    var totalDiff = lastX - startX;
    if (Math.abs(totalDiff) > window.innerWidth / 5 && (totalDiff * lastDX) <= 0) {
      if (totalDiff > window.innerWidth / 5 && lastDX <= 0) {
        window.impress().prev();
      } else if (totalDiff < -window.innerWidth / 5 && lastDX >= 0) {
        window.impress().next();
      }
    } else if (Math.abs(lastDX) > threshold) {
      if (lastDX < -threshold) {
        window.impress().prev();
      } else if (lastDX > threshold) {
        window.impress().next();
      }
    } else {

      // 没有移动——移动(后退)到当前的幻灯片
      window.impress().goto(document.querySelector("#impress .step.active"));
    }
  });

  document.addEventListener("touchcancel", function () {

    // 移动(后退)到当前幻灯片
    window.impress().goto(document.querySelector("#impress .step.active"));
  });

})(document, window);

/* global document */

(function (document) {
  "use strict";
  var toolbar = document.getElementById("impress-toolbar");
  var groups = [];

  /**
  * 获取作为工具栏子元素的 span 元素,该元素由索引标识。
  *
  * 如果 span 元素尚不存在,则创建它。
  *
  * 注意: 由于运行到完成,这不是一个竞争条件。
  * https://developer.mozilla.org/en/docs/web/javascript/eventloop#run-to-completion
  *
  * : param: index 方法将返回元素 span id"impress-toolbar-group- index"
  */
  var getGroupElement = function (index) {
    var id = "impress-toolbar-group-" + index;
    if (!groups[index]) {
      groups[index] = document.createElement("span");
      groups[index].id = id;
      var nextIndex = getNextGroupIndex(index);
      if (nextIndex === undefined) {
        toolbar.appendChild(groups[index]);
      } else {
        toolbar.insertBefore(groups[index], groups[nextIndex]);
      }
    }
    return groups[index];
  };


  /**
  * 从紧接在给定索引之后的组[]中获取 span 元素。
  *
  * 这可用于查找 insertBefore ()调用的引用节点。
  * 如果在较大的索引处不存在元素,则返回未定义的。 (在这种情况下,
  * 您可以使用 appendChild ()
  *
  * 注意索引本身不需要存在于组中[]。
  */
  var getNextGroupIndex = function (index) {
    var i = index + 1;
    while (!groups[i] && i < groups.length) {
      i++;
    }
    if (i < groups.length) {
      return i;
    }
  };

  // API
  // 其他插件可以通过将按钮作为事件发送来添加和删除按钮。
  // 作为回报,当添加按钮时,工具栏插件将触发事件。
  if (toolbar) {
    /**
    * 将小部件附加到工具栏 span 元素中,该元素由给定的组索引标识。
    *
    * : param: e.detail.group integer 指定将放置小部件的 span 元素
    * : param: e.detail.element a dom 元素添加到工具栏
    */
    toolbar.addEventListener("impress:toolbar:appendChild", function (e) {
      var group = getGroupElement(e.detail.group);
      group.appendChild(e.detail.element);
    });

    /**
    * 使用 insertBefore () DOM 方法向工具栏添加一个小部件。
    *
    * : param: e.detail.before the reference dom element,before which new element is added
    * : param: e.detail.element a dom 元素添加到工具栏
    */
    toolbar.addEventListener("impress:toolbar:insertBefore", function (e) {
      toolbar.insertBefore(e.detail.element, e.detail.before);
    });

    /**
    * 删除 e.detail.Remove 中的小部件。
    */
    toolbar.addEventListener("impress:toolbar:removeWidget", function (e) {
      toolbar.removeChild(e.detail.remove);
    });

    document.addEventListener("impress:init", function (event) {
      var api = event.detail.api;
      api.lib.gc.pushCallback(function () {
        toolbar.innerHTML = "";
        groups = [];
      });
    });
  } // 工具栏

})(document);

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注