假设您有一张世界地图,并且希望能够在光标悬停在某个国家/地区(或州、县等)上或点击时突出显示它。
可以做到!
首先,地图可能应该是矢量图。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();
});
});

移动设备支持
你听说过吗?(大多数)触摸屏上没有光标。
但是我们可以轻松地使其通过点击来工作,这也是 DRY 化的原因之一。除了绑定到mouseenter
和mouseleave
之外,我们还使用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 上。