2018-09-17_EstablishingNewViewports

理解SVG坐标系和变换(第三部分)-建立新视窗

(本文转自w3cplus,这里仅修正了部分个人认为翻译不恰当之处;下面是译文链接,英文原文链接在结尾处提供)

在SVG绘图的任何一个点,你可以通过嵌套<svg>元素或者其他元素来建立新的viewport和用户坐标系。在这篇文章中,我们将看一下我们如何这样做,以及这样做如何帮助我们控制SVG元素并让它们变得更加flexible(and/or fluid)。

这是SVG坐标系和变换系列的第三篇也是最后一篇文章。在第一篇中,包括了任何要理解SVG坐标系统基础的需要知道的内容;更具体的是, SVG viewport, viewBox和 preserveAspectRatio属性。在第二篇文章里,你可以了解到任何你需要了解的关于SVG系统变换的内容。

通过这篇文章,我假定你已经读了这个系列的第一部分关于SVG viewport, viewBox 和 preserveAspectRatio 属性的内容。在阅读这篇文章之前你不需要读第二篇关于坐标系变换的内容。

嵌套<svg>元素

第一部分我们讨论了<svg>元素如何为SVG画布内容建立一个viewport。在SVG绘图中的任何一点上,都可以通过在另一个< SVG >中包含< SVG >元素来创建一个新的视图,其中包含所有包含的图形。通过建立一个新的viewport,您还隐式地建立了一个新的viewport坐标系和一个新的用户坐标系。

例如,试想有一个<svg>及其里面的内容:

<svg xmlns = "http://www.w3.org/2000/svg" xmlns:xlink = "http://www.w3.org/1999/xlink" >
<!-- some SVG content -->
   <svg>
     <!-- some inner SVG content -->
   </svg>
</svg>

第一件需要注意的是内部<svg>元素不需要声明一个命名空间xmlns因为默认和外层<svg>的命名空间相同。当然,即使是外部<svg>元素,如果它是被嵌入在HTML5文档中不需要命名空间。

你可以使用一个嵌套的SVG来把元素组合在一起然后在父SVG中定位它们。现在,你也可以把元素组合在一起并且使用<g>group组来定位-通过把元素包括在一组<g>元素a group <g> element中。你可以使用transform属性在画布中定位它们。然而,使用一个<svg>元素必然优于使用<g>元素。使用x和y坐标来定位,在许多情况下,比使用变换更加方便。另外,<SVG>元素接受width和height, <g>元素不行。这意味着,<svg>元素可能并不总是需要或必须的,因为这会导致创建一个新的视图和坐标系统,您可能不需要或不希望这样做。

通过给<svg>元素指定宽高值,您可以将其中的内容限制在由x、y、width和height属性定义的viewport的范围内。任何超出这些范围的内容都将被裁切。

如果你不声明x和y属性,它们默认是0。如果你不声明height和width属性,会是父SVG宽度和高度的100%。

此外,指定一个非默认的用户坐标系统也会影响内部<svg>.

为内层<svg>内部的子元素指定的百分比值将会相对它(即内层<svg>)计算,而不是相对于外层<svg>。(译者注:请结合示例理解)例如,下面的代码会导致内层SVG等于400单位,里面的长方形是200个单位:

<svg width="800" height="600">
<svg width="50%" ..>
<rect width="50%" ... />
</svg>
</svg>

如果最外层<svg>的宽度设置为100%(例如,如果它在一个文档中内联或者你想要它可以流动),然后,内部SVG将根据需要进行扩展和收缩,以保持宽度为外部SVG的一半——这非常强大。

嵌套SVG在给SVG画布中的元素增加灵活性和扩展性时尤其有用。我们知道,使用viewBox值和preserveAspectRatio,我们已经可以创建响应式SVG。最外层的宽度可以设置成100%来确保它扩展拉伸到它的容器(或页面)扩展或拉伸。然后通过使用viewBox值和 preserveAspectRatio,我们可以保证SVG画布可以自适应viewport中的改变(最外层svg)。我在CSSConf演讲的幻灯片中写到了关于响应式SVG的内容。你可以在这里查看这个技术。

然而,当我们像这样创建一个响应式SVG,整个画布以及所有绘制在上面的元素都会有反应并且同时改变。但有时候,你只想让图形中的一个元素变为响应式,并且保持其他东西“固定”在一个位置和/或尺寸。这时候嵌套svg就很有用。

svg元素有独立于它父元素的坐标系,它可以有独立的viewBox和preserveAspectRatio属性,你可以任意修改里面内容的尺寸和位置。

所以,要让一个元素更加灵活,我们可以把它包裹在元素中,并且给svg一个弹性的宽度来适应最外层SVG的宽度,然后声明preserveAspectRatio="none"这样的话里面的图形会扩展和拉伸到容器的宽度。注意svg可以多层嵌套,但是为了让事情简洁,我在这篇文章里只嵌套一层深度。

为了演示嵌套svg如何发挥作用,让我们来看一些例子。

例子

试想我们有如下的SVG:

svg-nesting-example-1.png

上述SVG是响应式的。改变屏幕的尺寸会导致整个SVG图形根据需要做出反应。下面的截图展示了拉伸页面的结果,以及SVG如何变得更小。注意SVG的内容如何根据SVG视窗和相互之间保持它们的初始位置。

svg-nesting-example-1-resized.png

使用嵌套SVG,我们将改变这个情况。我们可以对SVG中每个独立的元素根据SVG视窗声明一个位置,所以随着SVG 视窗尺寸的改变(即最外层svg的改变),每个元素独立于其他元素发生改变。

注意,在这个时候,你需要熟悉SVG viewport, viewBox, 和preserveAspectRatio是如何生效的。

我们将要创建一个效果,当屏幕尺寸变化时,蛋壳的上部分移动使得其中的可爱的小鸡显示出来,如下图所示:

svg-nesting-example-1-new.png

为了达到这个效果,蛋的上半部分必须和其他部分分离出来单独包含一个自己的svg。这个svg包含框会有一个ID upper-shell

然后,我们保证新的svg#upper-shell和外层SVG有一样的高度和宽度。可以通过在svg上声明width="100%"height="100%"或者不声明任何高度和宽度来实现。如果内层SVG上没有声明任何宽高,它会自动扩展为外层SVG宽高的100%。

最终,为了确保上壳被“抬”起或定位在svg#upper-shell顶部的中心,我们将使用适当的preserveAspectRatio值来确保viewBox被定位在viewport的顶部中心-值是xMidYMin。

SVG图形的代码如下:

<svg version = "1.1" xmlns = "http://www.w3.org/2000/svg" xmlns:xlink = "http://www.w3.org/1999/xlink">

  <!-- ... -->
  < svg viewBox = "0 0 315 385" preserveAspectRatio ="xMidYMid meet">
    <!-- the chicken illustration -->
    <g id = "chicken">
      <!-- ... -->
    </g>

    <!-- path forming the lower shell -->
    <path id = "lower-shell" fill = "url(#gradient)" stroke = "#000000" stroke-width = "1.5003" d = "..."/>
  </svg>

 < svg id = "upper-shell" viewBox = "0 0 315 385" preserveAspectRatio = "xMidYMin meet">
<!-- path forming the upper shell -->
   < path id = "the-upper-shell" fill = "url(#gradient)" stroke = "#000000" stroke-width = "1.5003" d = "..."/>
 </svg>
</svg>

我已经删除了与本文相关的部分,比如用于给蛋壳上色的渐变和形成形状的路径,只是为了在示例代码中简洁起见。

此时,请注意嵌套的svg#upper shell中指定的viewBox的值与最外层的svg的值相同(在去掉它之前)。我们使用相同的viewBox值的原因是,SVG在大屏幕上保持了原来的外观。

所以,事情是这样的:我们从一个svg开始——在我们的例子中,是一个破裂的鸡蛋,里面藏着一只鸡。然后,我们创建另一个“层”,并将上层shell提升到—这个层是通过使用嵌套svg创建的。嵌套的svg具有与外部svg相同的维度和相同的视图框。最后,内在的viewBox SVG将“坚持”窗口顶部的不管什么屏幕大小—确保,当屏幕尺寸缩小和SVG是细长的,上壳向上将被取消,从而显示canvas"身后"的小鸡。

svg-nesting-example-1-layered.png

一旦屏幕尺寸拉伸,SVG被拉长,使用preserveAspectratio="xMidYMin meet"把包含上部分壳的viewBox被定位到viewport的顶部。

svg-nesting-example-1-viewbox.png

点击下面按钮来查看在线SVG。记住改变屏幕尺寸再看SVG变化。

在线案例

嵌套或"分层"SVG使你可以根据改变的视窗定位SVG的一部分,在保持元素宽高比的情况下。所以图片可以在不扭曲内容元素的情况下自适应。

如果我们想要整个鸡蛋剥离显示出小鸡,我们可以单独用一个svg层包含下部分壳,viewBox也相同。确保下部分壳向下移动并固定在视窗的底部中心,我们使用preserveAspectRatio="xMidYMax meet"来定位。代码如下:

<meta charset="utf-8">

<svg version="1.1" xmlns="[http://www.w3.org/2000/svg](http://www.w3.org/2000/svg)" xmlns:xlink="[http://www.w3.org/1999/xlink](http://www.w3.org/1999/xlink)">

    <svg id="chick" viewBox="0 0 315 385" preserveAspectRatio="xMidYMid meet">

        <!-- the chicken illustration -->

        <g id="chick">

            <!-- ... -->

        </g>

    </svg>

    <svg id="upper-shell" viewBox="0 0 315 385" preserveAspectRatio="xMidYMid meet">

        <!-- path forming the upper shell -->

        <path id="the-upper-shell" fill="url(#gradient)" stroke="#000000" stroke-width="1.5003" d="..."/>

    </svg>

    <svg id="lower-shell" viewBox="0 0 315 385" preserveAspectRatio="xMidYMax meet">

        <!-- path forming the lower shell -->

        <path id="the-lower-shell" fill="url(#gradient)" stroke="#000000" stroke-width="1.5003" d="..."/>

    </svg>

</svg>

每个svg层/viewport等于最外层svg宽高的100%。所以我们基本有了三个副本。每层包含一个元素-上部分壳,下部分壳,或小鸡。三层的viewBox是相同的,只有preserveAspectRatio不同。

svg-nesting-example-1-2.png

当然,在这个例子里,一开始的图形中小鸡隐藏在蛋里,随着屏幕变小才显示出来。然而,你可以做一些不一样的:你可以开始在小屏幕上创建一个图形,然后在大屏幕上显示一些东西;即当svg变宽时才有更多垂直空间来展示元素。

你可以更有创造性,根据不同屏幕尺寸来显示和隐藏元素-使用媒体查询-把新元素通过特定方式定位来达到特定的效果。想象力是无穷的。

同时注意嵌套svg不需要和容器svg有相同的宽高;你可以声明宽高并且限制svg内容,超出边界裁切-这都取决于你想要达到什么效果。

使用嵌套SVG使元素流动

在保持宽高比的情况下定位元素,我们可以使用嵌套svg只允许特定元素流动-可以不保持这些特定元素的宽高比。

例如,如果你只想SVG中的一个元素流动,你可以把它包含在一个svg中,并且使用preserveAspectRatio="none"来让这个元素扩展始终撑满这个视窗的宽,并且保持宽高比和像我们在之前例子中做的一样定位其他元素。

<svg>
    <!-- ... -->
    <svg viewBox=".." preserveAspectRatio="none">
        <!-- this content will be fluid -->
    </svg>
    <svg viewBox=".." preserveAspectRatio="..">
        <!-- content positioned somewhere in the viewport -->
    </svg>
    <!-- ... -->
</svg>

Jake Archibald创建了一个简单实用的嵌套SVG使用案例:一个简单的UI可以包含定位在最外层svg角落的元素,并且保持宽高比,UI的中间部分浮动并且根据svg宽度改变进行拉伸。你可以在这里查看。确保你在开发工具里检查代码来选取和想象不同viewbox和svg使用的效果。

其他建立新视窗的方法

svg不是唯一能在SVG中创建新视窗的元素。在下面部分,我们会讨论使用其他SVG元素创建新视窗的方法。

使用<use>ing<symbol> 建立一个新的视窗

只要由<use>元素实例化<symbol>元素,它就会定义一个新的视图。

symbol元素的使用可以参考use元素中的xlink:href属性:

<svg>
    <symbol id="my-symbol" viewBox="0 0 300 200">
        <!-- contents of the symbol -->
        <!-- this content is only rendered when `use`d -->
    </symbol>
    <use xlink:href="#my-symbol" x="?" y="?" width="?" height="?">
</svg>

上面值中的问号表示这些值也许没有声明-如果x和y没有声明,默认值为0,也不需要声明宽高。

看到了吧,当你use一个symbol元素,然后使用开发工具检查DOM,你不会看到use标签中symbol的内容。因为use的内容在shadow tree里被渲染,如果你在开发工具中允许shadow DOM显示你就能看到。

当symbol被使用时,它被深度克隆到生成的shadow tree中,例外是symbol被svg替换。这个生成的svg总是有明确的宽高。如果宽高的值在use元素上,这些值会被转换生成svg。如果属性宽和/或高没有声明,生成的svg元素会使用这些值的100%。

因为我们在DOM中使用了svg,并且因为这个svg实际上包含在外层svg中,我们遇到的嵌套svg的状况和我们在之前一章讨论到的并没有多少不一样-嵌套的svg形成了一个新的viewport。嵌套svg的viewBox是在symbol元素上声明的viewBox。(symbol元素接受viewBox元素值。更多信息,阅读这篇文章:Structuring, Grouping, and Referencing in SVG – The )

所以我们现在有了一个新的viewport,尺寸和位置可以使用元素(x,y, width, height)声明,viewBox值可以在symbol元素上声明。symbol的内容随后再这个视窗和viewBox中被渲染和定位。

最后,symbol元素也接收preserveAspectratio属性值,你可以在由use建立的新视窗中定位viewBox。这很清楚,不是吗?你可以像我们在之前的部分里一样控制新创建的嵌套svg。

Dirk Weber 也创建了一个使用嵌套SVG和symbol元素来模仿CSS border images的表现。你可以在这里查看文章。

通过在<image>中引用SVG图像来建立新的视图

images元素表明整个文件的内容被渲染到一个当前用户坐标系中给定的长方形。image元素可以代表图片文件例如PNG或JPEG或者有"image/svg+xml"的MIME类型的文件。

代表SVG文件的image元素会导致建立一个临时新视窗因为定义相关资源有svg元素。

<image xlink:href="myGraphic.svg" x="?" y="?" width="?" height="?" preserveAspectRatio="?" />

<image>元素接收许多属性,其中一些属性-和这篇文章有关的-是x和y位置属性,width和height属性以及preserveAspectratio。

通常,SVG文件会包含一个根元素;这个元素也许声明位置和尺寸,另外也许有viewBox和preserveAspectratio值。

当一个image元素代表SVG图片文件,根svg的x,y,width和height属性被忽略。除非image元素上的preserveAspectRatio值以“defer”开头,根元素上的preserveAspectRatio值在代表SVG图片时也被忽略。然而相关image元素上的preserveAspectRatio属性定义SVG图片内容如何适应视窗。

评估被参考内容定义的preserveAspectRatio属性时使用viewBox属性值。对于明确定义的viewBox内容(例如,最外层元素上有viewBox属性的SVG文件)值应该被使用。对于大多数值(PING,JPEG),图片边界应该被使用(即image元素有隐含的尺寸为'0 0 raster-image-width raster-image-height'的viewBox)。如果值不全的话(例如,外层的svg元素没有viewbox属性的SVG文件)preserveAspectRatio值被忽略,只有视窗x & y属性引起的移动才用来显示内容。

例如,如果一个image元素代表PNG或JPEG并且preserveAspectRatio="xMinYMin meet",那么栅格的宽高比会保持,栅格会在保证整个栅格适应视窗的情况下尽可能放大尺寸,栅格的左上角会和由image元素上x,y,width和height定义的视窗的左上角对齐。

如果preserveAspectRatio的值是“none”那么图片的宽高比不会保持不变。图片会自适应,栅格的左上角和坐标系(x,y)完全对齐,栅格的右下角和坐标系(x+width, y+height)完全对齐。

Establishing a new viewport using iframe

An iframe element that references an SVG file establishes new viewport similar to the situation of image element explained above. An iframe element can also have x, y, width, and height attributes, in addition to its own preserveAspectratio.

Establishing a new viewport using <foreignObject>

A foreignObject element creates a new viewport for rendering the content that is within the element.

The foreignObject tag allows you to add non-SVG content into an SVG file. Usually, the contents of foreignObject are assumed to be from a different namespace. For example, you could drop some HTML in the middle of an SVG element.

The foreignObject element accepts attributes, among which are x, y, height, and width, which are used to position the object and size it, creating the bounds used to render the contents referenced inside it.

There is a lot to say about the foreignObject element besides its creation of a new viewport for its content. If you’re interested, you can check the MDN entry or check this practical use case by Christian Schaeffer on The Nitty Gritty Blog.

Wrapping Up

Establishing new viewports and coordinate systems—be that by nesting svgs or another element from the ones mentioned above—allows you to control parts of the SVG that you would otherwise not be able to control the same way.

The entire time that I was working on this article and thinking of demos and use cases, all I kept thinking of is how nesting SVGs can give us finer control and flexibility for when we’re dealing with SVGs. Adaptive SVGs can be created with neat effects, fluid elements inside SVGs that are independent of the other elements on the page are possible, mimicing CSS border images for crispier backgrounds on high-resolution screens, and so much more.

Have you created any interesting examples using nested viewports in SVG? Can you think of more creative examples?

This article concludes the series of “Understanding SVG Coordinate Systems & Transformations”. Next up, we’ll be diving into animations, and more! Stay tuned, and thank you for reading!

Find similar articles under: #svg

原文链接:Understanding SVG Coordinate Systems and Transformations (Part 3) — Establishing New Viewports

推荐阅读更多精彩内容

  • 理解SVG坐标系和变换(第二部分)transform (本文转自w3cplus,这里仅修正了部分个人认为翻译不恰当...
    王策北阅读 79评论 0 1
  • 理解SVG坐标系和变换(第一部分)-viewport,viewBox,和preserveAspectRatio (...
    王策北阅读 89评论 0 1
  • 1. 前言 前端圈有个“梗”:在面试时,问个css的position属性能刷掉一半人,其中不乏工作四五年的同学。在...
    YjWorld阅读 3,136评论 5 12
  • 天气好的时候,阳光总是给它增添光亮与色彩 如果你有不顺心的时候,四处去看看,那些往往你一脚就能毁掉的风景,可不...
    红宛阅读 19评论 0 1
  • 礼物 文/张渝涵 在我的记忆中,我收到了很多礼物,包括我爸爸的战友和亲戚朋友,都送了我不少的礼物,但是,在我记忆深...
    童声童话阅读 151评论 0 2