Web Presentation,反向操作





5.00/5 (12投票s)
所有办公室演示应用程序的跨平台替代品的另一种变体,全部集中在一个文件中,现在这个文件是 JavaScript
(此演示使用了一个 AV1 视频,几乎所有浏览器都兼容,但 Microsoft Edge 除外)
目录
为什么是另一种变体?
用法
最简单的演示
文件名分隔符
具有不同类型帧的演示
视频选项
演示选项
HTML 内容创作建议
实现细节
收集演示数据
演示数据验证
保留样式
版本
致谢
许可说明
为什么是另一种变体?
这是关于单文件 Web Presentation 的第二篇文章
最初,CodeProject 会员 Helena Munzarova 阅读了我的 第一篇文章,并提出了一个不同的方法。我们进行了详细的讨论。她提出的方法是在一个 JavaScript 文件中提供解决方案,而不是像我的第一个解决方案那样是 HTML 文件。在这种情况下,用户需要提供 HTML 文件中的演示数据。
我的担忧是,以 HTML 形式编写演示文稿会要求用户具备更多的知识。尽管如此,在讨论过程中,我找到了一个方法来设计解决方案,使其对用户(演示文稿作者)保持极其简单。同时,有经验的用户可以使用高级 HTML 和 CSS 功能,并在单个 HTML 文档中设计出更高效、更具吸引力且易于维护的演示文稿帧。对 Helena 来说,主要的好处是增加了第三种类型的演示帧:HTML。前两种类型是用户在单独文件中提供的图形和视频,但 HTML 应由用户在同一个演示文件中开发,每个帧由一个单独的 HTML 元素及其 innerHTML
表示,我发现这足够容易实现。
但对我来说,“反向操作”方法一个更重要的好处是 用户数据的验证。
顺便说一下,本文顶部的图片和我演示中的四个新的高度动画化的演示帧都是 HTML 帧。
用法
最简单的演示
让我们来看一下演示样本“demo-the-other-way-around/minimal.html”
<html>
<head>
<script src="../presentation.js"></script>
<title>Minimal Presentation Demo</title>
</head>
<body>
"../demo-resources/diagram.svg"
"../demo-resources/workflow.svg"
"../demo-resources/photo.webp"
</body>
</html>
这种最简单的演示形式仅适用于有限类型的演示:图像文件序列,不带视频或用户定义的 HTML 帧。演示文稿的作者需要在其他元素的文本内容中添加额外的元素并列出一些文件,而不是在 body
中,而是以完全相同的方式。首先,我们需要弄清楚文件列表应该如何格式化。
文件名分隔符
上面显示的演示样本表明,图像文件应包含在双引号中。它们也用空格和换行符分隔,但这些字符将被忽略(除非它们包含在文件名中),并且不是必需的。另一种允许的分隔符是冒号“:
”。两种分隔符可以在同一个文件中使用。分隔符的使用遵循以下规则:
-
文本内容将被修剪;如果修剪后的字符串为空,则将其视为一个空文件数组。
-
如果修剪后的非空字符串的第一个字符是主要分隔符
"
,则使用该字符作为有效分隔符,否则,有效分隔符是次要分隔符“:
”。 -
整个文本字符串将使用有效分隔符进行拆分。拆分后的每个片段将被修剪,如果为空则忽略,否则将其添加到文件名列表中。
自然,这两种形式都允许文件名中包含空格字符。如果文件名包含其中一个分隔符字符,则应使用另一个字符作为分隔符。
建议:最好的办法是避免在文件名中使用任何分隔符字符。我甚至不建议在文件名中使用空格字符。对于这个特定的应用程序,一切都会正常工作,但对于许多其他应用程序来说,这会带来不便。而且,最终的决定是不使用任何分隔符,因为 每个文件名都可以放在一个单独的 HTML 元素中。那么,需要两个文件名的唯一情况是 带海报的视频帧。
也许我需要解释一下为什么规则会使用两个分隔符。首先,在 Windows 中,双引号不是有效的文件名字符,但冒号在我们都知道的特殊情况下使用。将冒号用作文件名的一部分意义不大,但用户仍然可以使用双引号输入这样的名称。这些引号通常也用于输入包含一些空格的文件名。
对于 *NIX(包括 Linux),只有 null 字符和“/
”是特殊的;所有其他字符都可以在文件名中使用。尽管如此,不同的应用程序可能会使输入包含冒号或双引号的名称变得困难,因为这些字符用于不同的特殊目的,与 Windows 中的情况非常相似。同时,冒号通常是 *NIX 配置、shell 和其他文件中的“路径分隔符”。
多个文件的集合在两种情况下使用:在单个 HTML 节点中指定图像文件列表,或指定视频文件名及其对应的海报文件名。视频的使用将在 下面详细解释。
具有不同类型帧的演示
上面显示的简单示例只是一个快捷方式,仅适用于仅由图像文件、矢量或栅格图形组成的演示。由于还有视频和 HTML 两种演示帧类型,我们需要一种方法来指示类型。为此,数据不是在 body
的文本内容中输入的,而是在 body
元素的直接子元素中输入的。如果 body
元素中使用了至少一个元素,则会忽略其文本内容。
要定义描述帧的元素,可以使用任何元素。事实上,即使是 HTML 标准未定义的标签名称也能工作;但是我不会依赖它。在我所有的演示和示例中,我都使用 <i>
元素,因为它是最短的名称,可以与“图像”、“项目”或“输入数据”等概念相关联。
让我们从一个简单的例子开始
<!-- ... -->
<body>
<!-- ... -->
<i class="image">single-vector-graphic-file.svg</i>
<i class="image">"image.0.webp" "vector.1.svg"</i>
<i class="image">vector.10.svg : vector.11.svg</i>
<i class="video" title="My trip">my-video-file.webm : a-poster-for-my-video.webp</i>
<i class="html">
<h1>Some HTML Content:<h1>
<p>Some paragraph<p>
</i>
</body>
</html>
在这里,要定义演示帧的类型,使用了 class
属性。在此上下文中,它与 CSS 无关。使用它是因为它是一个 全局属性,并且它的名称暗示了分类的概念,与“类型”的概念相关。此外,两个类可以组合在一个属性值中。
对于定义图像帧的元素,元素的内容应定义一个文件或一个文件列表。对于视频,必须是一个视频文件或两个文件的列表,如 视频选项部分 所解释。
对于 HTML 类型,当演示过程中显示相应的帧时,帧元素的 innerHTML
将被显示。此内容父元素为 <main>
。自然,用户可以提供适用于所有 HTML 帧的样式表。
有三种演示帧类型:图像、视频和 HTML。相应地,有三个类:image
、video
、html
,还有一个额外的类 autostart
,它仅与 video
结合使用,例如
<i class="video autostart"><!-- ... --></i>
<!-- same as: -->
<i class="autostart video"><!-- ... --></i>
自动启动是视频选项之一。所有视频选项将在下面详细介绍。
视频选项
视频只有三个选项:1)在激活其演示帧(不要与视频帧混淆)时可以自动启动,2)可以附带一个海报图像,3)当鼠标指针悬停在 <video>
元素上时,可以显示一个标题。默认情况下,不自动启动,也不显示海报或标题。以下示例不言自明
<!-- ... -->
<body>
<!-- ... -->
<i class="video" title="My trip">my-video-file.webm : a-poster-for-my-video.webp</i>
<i class="html"><h1>Demonstration of video auto-start...<h1></i>
<i class="video autostart" title="My trip">my-video-file.webm</i> <!-- no need for a poster -->
</body>
</html>
在这里,定义视频帧的元素应指定一个或两个文件;第一个文件始终是视频文件,第二个可选文件始终是海报图像文件。
title
属性及其值将从用户数据中收集,并用于 <video>
元素。
演示选项
演示选项在 <select>
元素中指定
<!-- ... -->
<body>
<!-- ... -->
<select>
<option value="false">hideHelpOnStart</option>
<option value="white">background</option>
<option value="false">rtl</option>
</select>
</body>
</html>
此示例中显示的定义是默认值。名称由 <option>
的文本内容表示,值由其 value
属性表示。任何这些选项都可以省略,<select>
元素也可以省略,然后使用默认值。因为最后相关行的值定义了有效选项值。文件中不应有超过一个 <select>
元素,它是 body
的直接子元素。
视频选项的应用 在上一篇文章中进行了描述。
HTML 内容创作建议
演示文稿与其他类型的文档非常不同。其他文档类型需要自由浮动的 HTML 内容,而演示文稿则要求内容适合单个页面。
对于 HTML 帧的样式设置,重要的是要记住,每个 HTML 帧的内容将成为单个 <main>
元素的 innerHTML
。即使演示作者可以更改任何其他元素(包括 <body>
)的样式,我也建议更改 main
和每个 HTML 演示帧内容的所有元素的样式。同时,有时更改 body
的背景颜色会很有用,因为它将定义所有 HTML 帧的背景颜色,对于其他帧,则使用 “背景”选项 定义的颜色。
为了简单起见,我还建议一种简单的技术:为每个 HTML 演示帧创建一个单个块级元素(例如 <section>
),并为其指定一个唯一的 id
属性值。所有其他帧内容都可以是该元素的嵌套内容。这样,CSS 可以使用单独的 ID 选择器 或所有这些 section 元素的 后代组合器 或 子组合器 来处理所有嵌套内容。这项技术可以轻松地消除相似元素样式的混乱。
使用 HTML 帧内容填充页面区域比普通样式设置需要更高级的样式。我建议考虑使用 flex
display
以及广泛使用 基于视口大小的相对单位:vw
、vh
、vmin
或 vmax
。
您可以在“demo-the-other-way-around/index.html”中找到许多技术,该演示已作为 实时演示。这并不意味着您应该展示我所做的所有动画技术。恰恰相反:最好尽量减少几乎所有地方的动画使用,或者根本不使用动画。我做所有这些动画是因为我的演示主题,需要展示许多可能性。对于其他主题的演示,最佳样式将是矢量图形与一些格式良好的文本的组合,没有过度的装饰。
实现细节
实现的主要部分在很大程度上与“presentation.html”相似。由于用户定义的 HTML 帧选项以及 保留样式 的需要,帧之间的移动方式略有不同。
而且,很自然,收集用户定义的演示数据的方法也不同。我们来看一下。
收集演示数据
代表演示帧的元素使用选择器 body > *:not(select)
收集。如果没有此类元素,则可能存在一种特殊情况,即 body
元素的文本内容中仅定义了图像类型的帧。在这两种情况下,都会获得一个列表 frameElements
。
const presentationFrameParser = selector => {
// ...
let frameElements = document.querySelectorAll(selector);
if (frameElements.length < 1) { //the fake content
const fakeElement =
document.createElement(frameTypeElement.fakeContent);
fakeElement.textContent = document.body.textContent;
frameElements = [fakeElement];
} //fake content
}; //
// ...
const frames = presentationFrameParser("body > *:not(select)");
然后,该元素列表由 presentationFrameParser
处理,以提取和 验证 演示帧数据。
同样,使用 <select>
元素的 选择器 来解析和 验证 选项。
const options = optionParser("body > select");
演示数据验证
“反向操作”方法的主要好处是,用户可能犯的所有错误都在 HTML 中。而对于 HTML……没有错误。任何错误内容都会进行优雅降级。这提供了一个很好的机会来分析用户数据的有效性,因为它们正在被收集。
所有分析都由 optionParser
和 presentationFrameParser
函数执行。如果验证失败,每个函数会立即返回一个描述错误的字符串值。如果所有验证都通过到解析结束,则函数返回帧和选项数据对象。这样,后续处理将基于返回结果的类型检查。
const optionParser = selector => {
// ...
// Options data:
const options = optionDefaults;
const select = document.querySelectorAll(selector);
// modify options using select data
// ...
};
window.onload = () => {
// ...
// Frame data:
const frames = [];
const frames = presentationFrameParser("body > *:not(select)");
// Option data:
const options = optionParser("body > select");
// populate frames using frameElements
// ...
if (frames.constructor == String)
return textUtility.showError(frames);
if (options.constructor == String)
return textUtility.showError(options);
// ...
// presentation starts here:
initializeViewer(/* ... */)
});
这样,在用户遇到第一个错误时,用户会得到详细的诊断信息,帮助修复遇到的第一个问题。在这种情况下,演示不会开始。这使得消除所有潜在的令人困惑的错误变得非常容易。
例如,验证会检查每个 class
属性是否定义了必需的类之一,或“video”和“autostart”的组合;是否为所有定义演示帧或帧集的元素指定了至少一个文件;每个 video
类元素是否定义了两个或更多文件;<select>
元素是否只有一个或缺失,等等。
保留样式
正如我所料,这种方法中最严重的问题在于用户(演示创建者)可以使用 HTML 帧并引入任何样式表。在我引入 HTML 演示帧选项之前,这无关紧要,因为所有 HTML 元素的样式都由产品代码控制。
隐藏应用程序帧所需的元素需要使用 display
样式属性,值为 none
。当元素需要显示时,应该赋予 display
属性什么值。在我引入 HTML 帧功能之前,它总是 block
,对 <img>
和 <video>
元素都适用。对于任意 HTML 内容,这没有意义,因为用户可以为用于演示帧的顶层 HTML 元素使用任何其他 display
值。例如,对于演示文稿特定的布局,flex
特别有用。当然,这个元素的初始样式可以在 window
内容 load
时检测到,但这并不是一个合理的复杂化。现在,帧元素被插入到 body
子元素列表中,并在不再需要时删除。
const move = backward => {
// backward == true => previous frame
// backward == false => next frame
// else => initialization (undefined passed)
// ...
if (currentFrameElement)
document.body.removeChild(currentFrameElement);
currentFrameElement = item.type == frameType.video ?
video : (item.type == frameType.image ? image : html);
document.body.insertBefore(
currentFrameElement,
document.body.firstElementChild);
};
它被插入而不是追加,原因如下:另一个 body
子元素是显示帮助项的元素。该元素具有 position: absolute
,并且可能与用户引入的任何其他 absolute
定位元素发生冲突。(可能的替代解决方案是使用 CSS z-index
属性。)
原则上,用户可以设计一个会破坏其他类型演示帧所需样式的样式表。为防止这种情况,document.body
和帮助元素的关键样式将得到保留。对于 document.body
,每次显示新的演示帧时都会恢复这些样式。
版本
4.0.0
2020年12月21日
这是 v. 3.0.0 后的第一个版本,也是第一个带有“presentation.js”,“反向操作”的版本。
4.2.0
2021年1月12日
在这两个产品中,帮助元素关闭框被替换为与平台无关的 SVG
4.3.0
2021年1月17日
修复了在 Gecko 和 Goanna 引擎上出现的关闭框大小问题。
4.4.0
2023年9月4日
修复了两个演示产品中的拼写错误
致谢
我感谢 Helena Munzarova 在 我上一篇文章 的评论中的讨论以及她启发我撰写本作品的想法。
Nelek 在两个演示产品中发现了一个 拼写错误。已在 v. 4.4.0 中修复。
许可说明
演示中使用的所有照片、视频和图形素材均由 本文作者 创建,并受下面引用的许可保护。