/*! 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 "▶"; // 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 & Right", text: "Previous & 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"><</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">></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);
本文链接地址: impress.js 1.0.0版本 源码注释翻译