SVG 地图悬停

Avatar of Chris Coyier
Chris Coyier

DigitalOcean 为您旅程的每个阶段提供云产品。开始使用 200 美元的免费积分!

假设您有一张世界地图,并且希望能够在光标悬停在某个国家/地区(或州、县等)上或点击时突出显示它。

可以做到!

首先,地图可能应该是矢量图。SVG 几乎可以肯定是在这里正确的图像格式选择。它将为我们提供清晰美观的地图,且尺寸合理,最重要的是,它能提供我们所需的交互性和轻松的样式设置。

让我们以美国州地图为例。 维基百科有一个完美的 供我们使用。

让我们下载该 SVG 并查看代码

这里有一些格式良好的 SVG 代码,每个州都由一个具有唯一 ID 的<path>表示。

最简单的地图悬停 将涉及将此 SVG 代码转储到 HTML 中,并在 CSS 中添加:hover,例如

path:hover {
  fill: red;
}

但是让我们更进一步。

双向悬停

我的意思是:您可以将鼠标悬停在州上,或者可以将鼠标悬停在列表中的州名称上,您会看到它被突出显示。

假设我们有一个像这样的州列表

<ul class="list-of-states">
  <li>Alabama</li>
  <li>Alaska</li>
  <li>Arizona</li>
  <li>Arkansas</li>
  ...

在从维基百科获取的 SVG 中,路径类似于

<path id="AK" fill="#D3D3D3" d="..." />

不幸的是,目前还没有简单的方法以编程方式连接这些元素。我手动创建了该连接,为列表中的每个州使用data-*属性

<ul class="list-of-states">
  <li data-state="AL">Alabama</li>
  <li data-state="AK">Alaska</li>
  <li data-state="AZ">Arizona</li>
  <li data-state="AR">Arkansas</li>
  ...

现在我们有了我们需要的东西。

让我们从列表项悬停到和悬停离开时都附加一个事件处理程序。当这些事情发生时,通过数据属性,我们将找出哪个 SVG 州是相关的,然后向两者添加类。

var wordStates = document.querySelectorAll(".list-of-states li");

wordStates.forEach(function(el) {

  el.addEventListener("mouseenter", function() {
    var stateCode = el.getAttribute("data-state");
    var svgState = document.querySelector("#" + stateCode);
    el.classList.add("on");
    svgState.classList.add("on");
  });

  el.addEventListener("mouseleave", function() {
    var stateCode = el.getAttribute("data-state");
    var svgState = document.querySelector("#" + stateCode);
    el.classList.remove("on");
    svgState.classList.remove("on");
  });

});

现在我们可以使用该类名来根据我们的需要设置每个元素的样式。

.list-of-states li.on {
  background: red;
  color: white;
  font-weight: bold;
}
path.on {
  fill: red;
}

我们也可以对州路径执行完全相反的操作,以便当我们将鼠标悬停在地图上时,列表中的州也会突出显示

var svgStates = document.querySelectorAll("#states > *");

svgStates.forEach(function(el) {

  el.addEventListener("mouseenter", function() {
    var stateId = el.getAttribute("id");
    var wordState = document.querySelector("[data-state='" + stateId + "']");
    el.classList.add("on");
    wordState.classList.add("on");
  });

  el.addEventListener("mouseleave", function() {
    var stateId = el.getAttribute("id");
    var wordState = document.querySelector("[data-state='" + stateId + "']");
    el.classList.remove("on");
    wordState.classList.remove("on");
  });

});

DRY 化

不幸的是,这有很多重复的代码。所以让我们清理它。我们可以将功能抽象成

  • 添加“on”状态(来自地图悬停)
  • 添加“on”状态(来自列表悬停)
  • 删除所有“on”状态

我们可以将它们制成函数,以便根据需要调用它们

var wordStates = document.querySelectorAll(".list-of-states li");
var svgStates = document.querySelectorAll("#states > *");

function removeAllOn() {
  wordStates.forEach(function(el) {
    el.classList.remove("on");
  });
  svgStates.forEach(function(el) {
    el.classList.remove("on");
  });
}

function addOnFromState(el) {
  var stateCode = el.getAttribute("data-state");
  var svgState = document.querySelector("#" + stateCode);
  el.classList.add("on");
  svgState.classList.add("on");
}

function addOnFromList(el) {
  var stateId = el.getAttribute("id");
  var wordState = document.querySelector("[data-state='" + stateId + "']");
  el.classList.add("on");
  wordState.classList.add("on");
}

wordStates.forEach(function(el) {
  el.addEventListener("mouseenter", function() {
    addOnFromState(el);
  });
  el.addEventListener("mouseleave", function() {
     removeAllOn();
  });
});

svgStates.forEach(function(el) {
  el.addEventListener("mouseenter", function() {
    addOnFromList(el);
  });
  el.addEventListener("mouseleave", function() {
     removeAllOn();
  });
});
hover states for all US states in red

移动设备支持

你听说过吗?(大多数)触摸屏上没有光标。

但是我们可以轻松地使其通过点击来工作,这也是 DRY 化的原因之一。除了绑定到mouseentermouseleave之外,我们还使用touchstart(点击也可以)。

wordStates.forEach(function(el) {
  // other vents

  el.addEventListener("touchstart", function() {
    removeAllOn();
    addOnFromList(el);
  });
});

svgStates.forEach(function(el) {
  // other events

  el.addEventListener("touchstart", function() {
    removeAllOn();
    addOnFromState(el);
  });
});

演示

查看 Chris Coyier 的 Pen
悬停状态
@chriscoyier
CodePen 上。