web api svg

While playing around with the Web Animation API it occurred to me that it might also be used for SVG line animations; I decided to apply it to an interactive roadtrip passing through three locations in my home country of New Zealand.

在使用Web动画API时,我发现它也可以用于SVG线条动画。 我决定将其应用于通过我的祖国新西兰的三个地点的交互式公路旅行。

This interactive is also progressive on mobile, in the sense that smaller devices do not attempt to show the map and locations in their limited viewports, but only show the location details.

从较小的设备不会尝试在其有限的视口中显示地图和位置,而仅显示位置详细信息的意义上来说,这种互动在移动设备上也是渐进的。

(The SVG)

I won’t go into tremendous depth on the SVG; you can see all the details on the associated CodePen.

我不会深入探讨SVG。 您可以在关联的CodePen上查看所有详细信息。

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 29.7 1061.9 1545.2" id="nzmap">
<g fill="teal">
    <path d="M668.9 …"/>
</g>
    <path id="ni" pathlength="240" d="M797.2…"/>
    <path id="si" pathlength="1250" d="M797.2 …"/>
    <a href="#">
        <circle cx="801.2" cy="488.9" r="17.2" class="outline" />
        <circle cx="801.2" cy="488.9" r="15" />
        <text dx="818.382" dy="439.046">Rotorua</text>
        <desc>
        Known for it’s geothermal activity, geysers and mud pools, 
        the air of Rotorua smells constantly of sulphur.
        </desc>
    </a>
    <a href="#">
        <circle cx="105.3" cy="1343.8" r="17.2" class="outline" style="animation-delay: 1s;" />
        <circle cx="105.3" cy="1343.8" r="15" />
        <text dx="160.028" dy="1360.957">Fiordland</text>
        <desc>
        A variant spelling of the Scandinavian word for the deep valleys 
        plunging into the ocean and bays…
        </desc>
    </a>
    <a href="#">
        <circle cx="635.9" cy="628.1" r="17.2" class="outline" 
            style="animation-delay: 1.2s;" />
        <circle cx="635.9" cy="628.1" r="15" />
        <text dx="395.53" dy="644.244">Taranaki</text>
        <desc>
        Taranaki is located on the lower third of the west coast of the 
        North Island, and is named for the stratovolcano that is its central feature.  
        </desc>
    </a>
</svg>

Several points from the SVG do need to be pointed out:

确实需要指出SVG的几点:

  • The three islands are a single path, filled with a teal color from the surrounding group.
  • The paths for the roadtrip elements are given different id values (representing “north island” and “south island”).
    给公路旅行元素的路径赋予不同的id值(代表“北岛”和“南岛”)。
  • The paths also use the pathLength attribute to describe the total length of each path, discovered through experimentation.
    这些路径还使用pathLength属性来描述通过实验发现的每个路径的总长度。
  • Links in the SVG surround the circle locations and the associated text; the <desc> content in each is not visible, but will be used by the JavaScript later.
    SVG中的链接围绕圆的位置和相关的文字 ; 每个中的<desc>内容都不可见,但稍后将由JavaScript使用。
  • The two circles in each link are styled and animated with CSS; the outer circle has an animation-delay so that all the locations don’t pulse at the same time.
    每个链接中的两个圆圈用CSS设置样式和动画; 外圈具有animation-delay因此所有位置都不会在同一时间脉动。

(The HTML)

The SVG is inline in the page, and surrounded with a <div>; there is another <div> for the location description and image:

SVG在页面中内联,并用<div>包围; 位置描述和图像还有另一个<div>

<div id="map">
    <svg …></svg>
    <div id="description">
    </div>
</div>

(The CSS)

The stylesheet sets up the animations that will be used:

样式表设置将使用的动画:

@keyframes fadein {
  to { opacity: 1; }
}
@keyframes lookit {
  to {
    stroke-width: 20px;
    stroke: rgba(255, 255, 255, 0.2);
  }
}

Then the CSS addresses relationship between the map and the description alongside it. Note that the elements inside the description are delayed when it has a class of active, so that they appear one after the other when the script makes them visible:

然后,CSS处理地图及其旁边的描述之间的关系。 需要注意的是当它有一个类的说明里面的内容被延迟active ,让他们出现一前一后,当脚本使它们可见:

#map svg {
  margin-left: 2rem;
  width: 33%;
  height: auto;
  display: inline-block;
}
#description {
  display: inline-block;
  background: rgba(0, 0, 0, 0.6);
  color: #fff;
  width: 50%;
  margin: 1rem 3rem 0;
  vertical-align: top;
  transition: .8s cubic-bezier(.14, .39, .41, 1.29);
}
#description * { opacity: 0; }
#description p { margin-bottom: 2rem; }
#description.active * {
  animation: fadein 3s forwards;
}
#description.active img {
  animation-delay: .4s;
}
#description.active h1 {
  animation-delay: 1s;
}
#description.active p {
  animation-delay: 2s;
}
#description h1,
#description p {
  margin-left: 2rem;
  margin-right: 2rem;
}
#description img {
  width: 100%;
}

Then finally the map, text and location circles; the circles have a custom easing curve for a “pulsing” motion:

最后是地图,文字和位置圈; 圆具有“脉动”运动的自定义缓动曲线:

#map text {
  font-size: 48px;
  font-weight: 400;
  fill: #fff;
}
#map circle {
  fill: #000;
}
#map circle.outline {
  fill: none;
  stroke-width: 5px;
  stroke: #fff;
  transition: .4s;
  animation: lookit 1.8s infinite alternate cubic-bezier(.85, 0, .32, .83);
}
#map a:hover circle:not(.outline),
a:focus circle {
  fill: #f00;
}

…and travel paths. Each of the paths have the same values for stroke-dasharray and stroke-dashoffset, meaning that they don’t have any visible stroke:

…以及行进路线。 每个路径的stroke-dasharraystroke-dashoffset值都相同,这意味着它们没有任何可见的笔触:

#ni, #si {
  fill: none;
  stroke: #f00;
  stroke-width: 12px;
  transition: .4s opacity;
}
#ni {
  stroke-dasharray: 240;
  stroke-dashoffset: 240;
}
#si {
  stroke-dasharray: 1250;
  stroke-dashoffset: 1250;
}

(The JavaScript)

If the user clicks anywhere on the SVG map, the script uses event bubbling to filter out registering anything other than a link. It then grabs the text associated with the clicked link, test that the user hasn’t clicked on the same link twice, and animates the appropriate travel path. Finally, the description div is filled with the desc content:

如果用户单击SVG地图上的任意位置,脚本将使用事件冒泡过滤掉注册链接以外的任何内容。 然后,它获取与单击的链接相关联的文本,测试用户没有两次单击同一链接,并为相应的移动路径设置动画。 最后, description div中填充了desc内容:

let prevLoc, locName;
    
nzmap.addEventListener("click", function(e) {
    let loc = e.target.parentNode;
    e.preventDefault();
    let desc = loc.querySelector("desc").textContent;
    if (loc.nodeName == "a") {  
        locName = loc.querySelector("text").textContent;
        if (locName !== prevLoc) { 
            if (locName == "Taranaki") {
                pathTravel(ni, ni.pathLength.baseVal);
            }
            if (locName == "Fiordland") {
                if (prevLoc == "Taranaki") {
                    pathTravel(si, si.pathLength.baseVal);
                } else {
                    pathTravel(ni, ni.pathLength.baseVal, "true");  
                }
            } 
        description.classList.remove("active");  
        description.innerHTML = "";
        prevLoc = locName; 
        description.insertAdjacentHTML("afterbegin", fillDesc(locName, desc));
        description.classList.add("active");
        }
    }
});

That description is filled with a function that creates a srcset based on the name used in the <text> element of the clicked link, together with the description (based on the <desc> content).

该描述充满了一个函数,该函数根据单击链接的<text>元素中使用的名称以及描述(基于<desc>内容)创建srcset图像

function fillDesc(locName, desc) {
    var imageURL = "",
    fileName = locName.split("."),
    longDesc = "<img src="+imageURL + locName.toLowerCase() +".jpg srcset=""+imageURL + fileName[0].toLowerCase() +".jpg 2x" alt>";
    longDesc += "<h1>"+locName+"</h1>";
    longDesc += "<p>"+desc+"</p>";  
    return longDesc;
}

To animate the travel paths I used the Web Animation API:

为了给行进路径设置动画,我使用了Web Animation API:

function pathTravel(travelPath, pathDist, extra) {
    travelPath.animate([
        { strokeDashoffset: pathDist },
        { strokeDashoffset: "0" }
        ], {
        duration: pathDist * 5,
        fill: "forwards"
    }).onfinish = function() {
        if (extra) {
            pathTravel(si, si.pathLength.baseVal); 
        }
    extra = false;
    }
}

In most cases, the growth of the travel path will be from a northern location to the current location, but in the case of Fiordland I needed the travel to involve both paths. To make this happen, I used the Web Animation API’s onfinish method to test if that needed to happen; if it did, I called the function again to trace the route from Taranaki to Fiordland after the Rotorua-Fiordland path was complete.

在大多数情况下,行进路径的增长将是从北部位置到当前位置,但是在峡湾地区,我需要将两条路径包含进去。 为了实现这一点,我使用了Web Animation API的onfinish方法来测试是否需onfinish 。 如果可以,我在Rotorua-Fiordland路径完成 再次调用该函数以跟踪从Taranaki到Fiordland的路线。



Note that I haven’t reset the paths after clicking on each location for the sake of simplicity, although that wouldn’t be too much more work to accomplish.

请注意,为简单起见,在单击每个位置后,我没有重置路径,尽管这并不需要太多工作。



(Mobile Accomodations)

No matter how much fun it is, some UIs just aren’t built for smaller screens: this example is one of them. To address that, I’ve used a matchMedia to see if the browser is under 750 pixels wide. If it is, I fill the description div with the text and pictures of all the locations:

不管它有多有趣,某些UI都不是为较小的屏幕构建的:此示例就是其中之一。 为了解决这个问题,我使用matchMedia检查来查看浏览器的宽度是否低于750像素。 如果是的话,我将所有位置的文字和图片填充到description div中:

var screencheck = window.matchMedia("(max-width: 750px)");
window.addEventListener("load", function() {
    if (screencheck.matches) { 
        let locs = nzmap.getElementsByTagName("text"),
        descs = nzmap.getElementsByTagName("desc");
        for (let i = 0; i < locs.length; i++) {
            description.insertAdjacentHTML("afterbegin", "<div>"+fillDesc(locs[i].textContent, descs[i].textContent)+"</div>");
        }
     }
});

…and in my CSS, use an @media combined with flexbox to display the result, while hiding the map:

…在我CSS中,结合使用@media查询flexbox来显示结果,同时隐藏地图:

@media all and (max-width: 790px) {
  #nzmap {
    display: none;
  }
  #description {
    width: 100%;
    margin: 0;
  }
  #description * {
    opacity: 1;
  }
  #description > div {
    flex: 1;
  }
}

(Conclusion)

This interface was originally designed to support a video roadtrip using WebVTT chapters; I’ll eventually get to that, but hope that you’ll find this version sufficiently interesting in the meantime.

该接口最初被设计为支持使用WebVTT章节视频 旅行 ; 我最终会讲到这一点,但是希望与此同时,这个版本足够有趣。

翻译自: https://thenewcode.com/1154/Create-an-Interactive-Roadtrip-with-SVG-and-the-Web-Animation-API

web api svg