SVG 文本教程:Web 上的文本注释

已发表: 2022-03-11

借助 HTML5 和 CSS3,Web 浏览器获得了许多令人惊叹的技术:3D 图形、套接字、线程等等。 有了这些,Web 应用程序可以利用它们所使用的计算机和操作系统的一些最复杂的功能。 Web 浏览器为应用程序开发提供了一个强大的多功能生态系统,这从最近大量强大的 Web 应用程序的兴起中可以看出,我们离不开这些应用程序。 然而,仍然缺少的东西是 HTML 文本注释和装饰的美感。 什么是文字装饰? 波浪形的下划线、粗犷的高光和波浪形的三线划线是 Web 浏览器不提供本机支持的一些内容。 这听起来可能比有用更复杂,但是 JavaScript 开发人员生成这些样式的能力可能在诸如电子学习资源和基于 Web 的电子书阅读器等方面证明是有用的。 此外,这有助于增强围绕自然设计原则的 Web 应用程序的用户体验。 至少,构建这样的工具很有趣,并且可以深入了解 Web 浏览器的许多怪癖。

SVG 文本教程 - 文本注释

开发人员发现了许多解决 Web 浏览器限制的方法。 许多这些变通方法涉及以不太直观的方式使用 CSS,因为有些在“::after”伪元素中使用图像。 这行得通,但是为每个样式-颜色对维护许多图像通常被证明是困难的。 本文将剖析一个试图优雅地解决这个问题的 JavaScript 库。

该库是开源的,可在 GitHub 上获得:Text Annotator

概述

在开发这个库时,特别注意确保与最流行的网络浏览器(包括 IE 9+)的兼容性。 然而,与大多数解决此问题的方法不同,该库不依赖于特别晦涩的 CSS 技巧。 或更糟糕的是,特殊的 Unicode 符号。 相反,它使用 SVG 来实现更好、更清晰的文本装饰。

从根本上说,该库实现了一个注释器“类”,可用于自动创建 DIV 元素,将它们放置在要注释的文本下,并用 SVG 图像填充它们的背景。 可以组合多个 DIV 以进一步自定义装饰。 该方法是跨浏览器兼容的,提供了装饰元素定位的灵活性,并允许使用自定义模板进行更轻松的扩展。

该库是使用 Google Closure Tools 开发的,因为它是模块化和跨浏览器的,有助于生成紧凑且快速的 JavaScript 代码,而无需任何额外的依赖。

建筑学

该库被设计为 JavaScript“类”的集合,并通过“类”注释器向用户公开所有必要的功能:

文本注释器库

以下是可用功能的简要概述:

  • annotateDocument - 对标有“data-annotate”属性的元素进行注释。

  • underline - 下划线元素

  • highlight - 突出显示元素

  • 罢工 - 罢工元素

  • underlineSelected - 给选中的文本加下划线

  • highlightSelected - 突出显示选定的文本

  • 罢工选择 - 罢工选定的文本

  • unannotateElement - 从元素中删除注释

  • getTemplates - 返回注释模板的字典

  • setUnderlineOptions - 设置下划线注释器的设置

  • setHighlightOptions - 设置高亮注释器的设置

  • setStrikeOptions - 设置罢工注释器的设置

注释器类为每个注释函数保存 AnnotatorImpl 类的三个实例:下划线、突出显示和罢工。

 tvs.Annotator = function() { this.underliner_ = new tvs.AnnotatorImpl( 'underliner', tvs.Annotator.getTemplates(), tvs.AnnotatorCore.underlinePositioner); this.highlighter_ = new tvs.AnnotatorImpl( 'highlighter', tvs.Annotator.getTemplates(), tvs.AnnotatorCore.highlightPositioner, {opacity: 0.45}); this.striker_ = new tvs.AnnotatorImpl( 'striker', tvs.Annotator.getTemplates(), tvs.AnnotatorCore.strikePositioner); };

AnnotatorImpl 实例是使用不同的 ID 和定位器辅助对象创建的。 传递的 ID 稍后用于 CSS 类名称和内部字段名称,要求 ID 是唯一的。 此外,还传递了对已知模板列表的引用(以后可以更改)。

每个定位器对象都是 IPositioner 接口的一个实现,它只有“getPosition”方法,如下所示:

 /** * Underline positioner * @implements {tvs.IPositioner} */ tvs.AnnotatorCore.underlinePositioner = /** @type {!tvs.IPositioner} */ ({ /** * @param {Object} elementRect * @param {number} annotationHeight * @return {{left: number, top: number, width: number, height: number}} */ getPosition: function(elementRect, annotationHeight) { return { width: elementRect.width, height: annotationHeight, left: elementRect.left, top: elementRect.bottom - (elementRect.height * 0.1) }; } });

这允许每个模板与下划线、突出显示或删除文本注释一起使用。 当注释应用于元素时,通过调用“getElementRects”获取元素的边界框,如下所示:

 var rects = elemOrEv.getClientRects();

此方法返回一个矩形集合,这些矩形表示客户端中每个框的边界矩形。 将每个矩形传递给具体的定位器后,我们将获得目标边界。

SVG 文本注释模板

如前所述,只有一组模板可用于各种 SVG 文本注释。 每个模板都由模板部分组成。 模板部件是表示部件内容、模板宽度和绘制模式的实体。

内容

内容是一组表示为字符串的 SVG 元素。 由于此内容没有设置视口宽度和高度(以像素为单位)的根 SVG 节点,因此模板的部分构造函数接受它们作为参数。 例如,您可以将视口的大小指定为 100px x 100px,并在 (50, 50) 和 (25, 25) 之间画一条线。 应用注释后,所有 svg 元素将正确调整为所需大小。 内容值可以使用字符串“{0}”,该字符串将替换为用户选择的颜色。

以下 SVG 呈现对角线。 我们将在接下来的示例注释样式中使用它作为其中的一部分:

 <line x1="0" y1="0" x2="5" y2="5" stroke-width="2" stroke="red" />

宽度

模板宽度是一个字符串,可以是“*”、“height”或其他任何内容:

  • “*”设置所有带星号的元素的宽度彼此相等

  • “height”设置宽度等于注释元素的高度

此处设置的任何其他内容将直接设置为 CSS width 和 min-width 属性。

CSS 属性

绘图模式

绘制模式是一个可以是“重复”或“拉伸”的字符串。 如值所示,将其设置为“重复”将重复内容,而将其设置为“拉伸”将拉伸内容。

以下是我们通过配置这三个参数可以实现的示例:

文本注释

上例中的文本注释包含 4 个部分。 第一部分是对角线,模板宽度设置为“高度”,绘制模式设置为“重复”。 第二部分的模板宽度设置为“*”,绘制模式设置为“重复”。 第三部分设置为“15px”宽并以“重复”模式绘制。 最后,将最后一部分的宽度设置为“*”,并将其绘制模式设置为“拉伸”。

在评估这些宽度时,第一部分占用 5 个像素(等于注释元素的高度),第三部分占用 15 个像素(按设置),剩余空间在第二部分和第四部分之间平均分配。

当使用相同的模板突出显示相同的文本时,这就是我们得到的:

绘图模式

如您所知,注释元素的高度更大,第一部分的宽度也更大(因为该部分的模板宽度设置为“高度”)。 自然地,第三部分的宽度与前面的示例保持不变。

对具有相同模板的相同文本应用删除效果会产生与第一个非常相似的结果。 唯一的区别是注释元素的位置:

文本注释绘制模式

尽管这些文本注释看起来很复杂(它们的外观,有四个不同的部分),但它们都使用非常简单的 SVG 元素。 再举一个例子,做一条波浪线需要一个单独的部分,包含以下简单的 SVG 内容:

 var t = new tvs.Template(new tvs.SvgTemplatePart( '<line y2="16.00" x2="20" y1="4.00" ' + 'x1="10" stroke-linecap="round" ' + 'stroke-width="5" stroke="{0}" fill="none"/>' + '<line y2="4.00" x2="10" y1="16.00" ' + 'x1="0" stroke-linecap="round" ' + 'stroke-width="5" stroke="{0}" fill="none"/>', 20, 20, 'repeat' ))

评估这些模板时,会调整内容的大小并自动将“{0}”替换为指定的颜色。 更重要的是,添加新模板就像将它们添加到 JavaScript 对象一样简单:

 tvs.AnnotatorDictionary.svgTemplates['brush'] = new tvs.Template(new tvs.SvgTemplatePart( svgContent, 50, 50, '*', 'stretch' ));

结果

每个注释都是通过将具有绝对定位的 div 元素附加到页面来应用的:

 <div class="tvs-annotate-element"> <div class="tvs-wrap-div"> <table> <tr> <td></td> <td></td> <td></td> </tr> </table> </div> </div>

div 元素填充有一个表格,其中添加的每个单元格都对应于模板中的一个部分。 每个模板部分的内容都添加为 Base64 编码数据 URI,并应用所选颜色:

 tvs.SvgTemplatePart.prototype.getBackground = function(color) { var image = tvs.AnnotatorCore.formatString(this.content, [color]); var encodedSVG = goog.crypt.base64.encodeString(image); return 'data:image/svg+xml;base64,' + encodedSVG; };

嵌入

为了获得更好的用户体验,尤其是在尝试将此 JavaScript 库用于可编辑内容区域时,文本注释器了解用户当前选择的文本范围非常重要。 Rangy 是一个处理范围和选择的简洁 JavaScript 库,已用于以跨浏览器的方式实现此目的。 Rangy 提供了一个简单的基于标准的 API,用于在所有主要浏览器中执行常见的 DOM 范围和选择任务,抽象出 Internet Explorer 和 DOM 兼容浏览器之间该功能的完全不同的实现。 它是项目的唯一依赖项。

嵌入文本注释器后,使用它是一个非常简单的壮举:

 var annotator = new tvs.Annotator(); annotator.underlineSelected();

每个带注释的元素都标有“tvs-annotated-text”类,每个注释元素都有“tvs-annotate-element”类。 删除注释更简单,单行:

 annotator.unannotateElement(annotatedElement);

怪癖

调整窗口大小时,元素可能会四处移动,需要“刷新”带注释的元素。 这是由图书馆处理的。 然而; 为了减少对性能的影响,对刷新注释的调用受到限制:

 tvs.AnnotatorImpl = function(id, templates, positioner, options) { // ... this.throttle = new goog.Throttle(goog.bind(this.refreshAllAnnotations, this), 50); tvs.AnnotatorCore.registerForWindowResize( this.id,goog.bind(this.throttle.fire, this.throttle)); }; tvs.AnnotatorImpl.prototype.refreshAllAnnotations = function() { var elems = goog.dom.getElementsByClass(this.getCssClassForAnnotated()); var refFunc = goog.bind(this.refreshAnnotation, this); goog.array.forEach(elems, refFunc); };

刷新后,可以根据需要从页面中添加、调整大小或删除注释元素。

方便

为了更容易在页面上注释静态文本,容器元素上的一个简单数据属性就足够了:

 data-annotate='underline squiggly green'

这将使用弯曲的绿色下划线注释元素的内容。

结论

关于这个 SVG 文本教程,我还能说什么? 一个有趣但功能强大的工具已轻松实现。 我认为确保对 Internet Explorer 8 的支持不会给我们带来太多好处,因为我们最终可能会使整个实现变得复杂。 但是,通过对核心的一些改进和一些工作,我们可以扩展库以能够为非文本元素生成装饰性边框。 此外,实现某种机制来保存和稍后恢复注释的可编辑内容的状态可能是一项有趣的任务。

就目前而言,可能性仅受您的想象力(和浏览器功能)的限制。 也许您想要微缩印刷线条、渐变甚至动画。 使用文本注释器,您可以。