在 Hover 時更改 SVG 顏色的幾種方式

Eason Lin
11 min readJan 15, 2023

--

Photo by Calvin Mano on Unsplash

大家好,這篇我們來紀錄一下可以讓 SVG 在滑鼠移動到其之上換色的幾種方法。SVG 全稱為可縮放向量圖形,其優點如下:

  • 撇除非常小的尺寸,SVG 在任何尺寸下都不會失真
  • 本質上為 XML 標記語言,這表示透過文字編輯器即可修改其內容
  • 可和 HTML、CSS 及 JavaScript 一同運作

使用 <img> 引入

img 標籤讓我們可以在 HTML 文件中嵌入圖片,可嵌入的圖片就包含了 SVG。引入的方式如下:

<img src="/edit.svg" width="48" height="48" />

若要讓它在 Hover 時換色,我們需要先準備好另一個已經換色的 icon,並透過 JS 綁定監聽器:

var img = document.querySelector('img')
img.addEventListener('mouseenter', function() {
this.src = '/edit-silver.svg'
})
img.addEventListener('mouseleave', function() {
this.src = '/edit.svg'
})

這樣做的好處有:

  1. 瀏覽器支援度非常高,已經入土的 IE 都能支援
  2. 在 Hover 時瀏覽器才會對資源發出請求,可減少請求的資源數量

甚至可以直接在 HTML 上就完成:

<img src="/edit.svg" alt="" width="48" height="48" onmouseenter="this.src='/edit-silver.svg'" onmouseleave="this.src='/edit.svg'">

如果圖源是不一定存在的,還能做一層 Fallback 處理:

<img src="/not-existed-icon.svg" alt="" width="48" height="48" onerror="this.src='/edit.svg'">

缺點則是:

  1. 需要有換過色的 SVG 圖檔
  2. 必須仰賴 JavaScript 才能處理,在遵循關注點分離的情況下,若 HTML 經過異動但未同步調整 JavaScript,就有可能造成對 null 呼叫 addEventListener 而報錯的問題。

使用 background-image 語法

我們可以使用 background-image 配合 class,例如:

<i class="edit-icon"></i>
.edit-icon {
display: inline-block;
background-image: url(/edit.svg);
background-repeat: no-repeat;
background-size: cover;
width: 48px;
height: 48px;
}
.edit-icon:hover {
background-image: url(/edit-silver.svg);
}

這樣做的好處有:

  1. 瀏覽器支援度非常高,也是我在產品仍須支援 IE11 時採用的作法
  2. 處理上由 CSS 包辦,不會因為 HTML 異動而產生影響使用的問題
  3. 在 Hover 時瀏覽器才會對資源發出請求,可減少請求的資源數量

缺點則是:

  1. 需要有換過色的 SVG 圖檔
  2. 無法使用 transition 做到漸變的效果

將 SVG 寫入 HTML 中

前面提到 SVG 本質上是標記語言,當我們使用編輯器打開 SVG 時顯示的也會是一段語法而非亂碼,這表示我們可以直接在 HTML 中嵌入:

<svg width="48px" height="48px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_8_1311)">
<path d="M20.4013 6.76711L17.233 3.5988C16.3581 2.7239 14.9396 2.7239 14.0647 3.5988L3.63175 14.0317C3.2116 14.4518 2.97557 15.0217 2.97557 15.6159L2.97557 19.9043C2.97557 20.523 3.47709 21.0245 4.09574 21.0245L8.38421 21.0245C8.97838 21.0245 9.54822 20.7885 9.96837 20.3683L20.4013 9.93542C21.2762 9.06052 21.2762 7.64202 20.4013 6.76711Z" stroke="#1C1C1C" stroke-width="1.7" stroke-linecap="round"/>
<path d="M15.4896 21.0574H19.9703" stroke="#1C1C1C" stroke-width="1.7" stroke-linecap="round"/>
</g>
<defs>
<clipPath id="clip0_8_1311">
<rect width="24" height="24" fill="white"/>
</clipPath>
</defs>
</svg>

這樣做的好處有:

  1. 不需要額外對資源發出請求
  2. 可直接透過 CSS 的 fill 讓 SVG 換色,也可使用 transition 達到漸變效果:
<svg class="my-edit-icon" width="48px" height="48px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_8_1311)">
<path d="M20.4013 6.76711L17.233 3.5988C16.3581 2.7239 14.9396 2.7239 14.0647 3.5988L3.63175 14.0317C3.2116 14.4518 2.97557 15.0217 2.97557 15.6159L2.97557 19.9043C2.97557 20.523 3.47709 21.0245 4.09574 21.0245L8.38421 21.0245C8.97838 21.0245 9.54822 20.7885 9.96837 20.3683L20.4013 9.93542C21.2762 9.06052 21.2762 7.64202 20.4013 6.76711Z" stroke="#1C1C1C" stroke-width="1.7" stroke-linecap="round"/>
<path d="M15.4896 21.0574H19.9703" stroke="#1C1C1C" stroke-width="1.7" stroke-linecap="round"/>
</g>
<defs>
<clipPath id="clip0_8_1311">
<rect width="24" height="24" fill="white"/>
</clipPath>
</defs>
</svg>
.my-edit-icon {
fill: #fff;
transition: fill 0.2s linear;
}
.my-edit-icon:hover {
fill: silver;
}

我們還可以為 stroke,也就是類似框線的線條自訂顏色,讓它在 hover 時也能有漸變的效果:

<path class="my-edit-icon-pen" d="M20.4013 6.76711L17.233 3.5988C16.3581 2.7239 14.9396 2.7239 14.0647 3.5988L3.63175 14.0317C3.2116 14.4518 2.97557 15.0217 2.97557 15.6159L2.97557 19.9043C2.97557 20.523 3.47709 21.0245 4.09574 21.0245L8.38421 21.0245C8.97838 21.0245 9.54822 20.7885 9.96837 20.3683L20.4013 9.93542C21.2762 9.06052 21.2762 7.64202 20.4013 6.76711Z" stroke="#1C1C1C" stroke-width="1.7" stroke-linecap="round"/>
.my-edit-icon:hover .my-edit-icon-pen { 
stroke: red;
}
.my-edit-icon-pen {
stroke: #000;
transition: stroke 0.2s linear;
}

壞處則是:

  1. 不須對資源發出額外請求也意味著無法快取資源
  2. 大量使用可能導致 HTML 肥大而難以閱讀

還記得以前我曾經經手過一個專案,它便是使用 SVG 去呈現某種遊樂設施,原始作者便是將 SVG 直接寫在 HTML 檔案中。光是那段遊樂設施的原始碼就有上萬行,每次打開那隻檔案都是一種對文字編輯器的考驗。

使用 <object> 引入

<object> 用來引入外部資源,資源可以是圖片、一段上下文或資源,例如:

<object type="image/svg+xml" data="/edit.svg" width="48" height="48">Edit Icon</object>

我們可以直接將想要的漸變寫在 svg 的 <style> 中:

<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg class="my-edit-icon" width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<style>
.my-edit-icon {
fill: #fff;
transition: fill 0.2s linear;
}
.my-edit-icon:hover {
fill: silver;
}
</style>
<g clip-path="url(#clip0_8_1311)">
<path d="M20.4013 6.76711L17.233 3.5988C16.3581 2.7239 14.9396 2.7239 14.0647 3.5988L3.63175 14.0317C3.2116 14.4518 2.97557 15.0217 2.97557 15.6159L2.97557 19.9043C2.97557 20.523 3.47709 21.0245 4.09574 21.0245L8.38421 21.0245C8.97838 21.0245 9.54822 20.7885 9.96837 20.3683L20.4013 9.93542C21.2762 9.06052 21.2762 7.64202 20.4013 6.76711Z" stroke="#1C1C1C" stroke-width="1.7" stroke-linecap="round"/>
<path d="M15.4896 21.0574H19.9703" stroke="#1C1C1C" stroke-width="1.7" stroke-linecap="round"/>
</g>
<defs>
<clipPath id="clip0_8_1311">
<rect width="24" height="24" fill="white"/>
</clipPath>
</defs>
</svg>

這樣做的好處有:

  1. 可以解決原始碼上 HTML 肥大難以閱讀的問題
  2. 因為是作為外部資源被引入,其是可以被快取的

壞處則是:

  1. 由於必須直接異動 svg 圖檔的內容,若有一個 svg 但希望在不同區塊有不同效果就需要把檔案拆分開來

以上就是關於在不同情境下我們可以怎麼透過什麼方式讓 SVG 在 hover 時換色,希望能幫助到大家。如果內文有任何錯誤或需要補充,也歡迎留言告訴我,感謝大家。

References:

https://developer.mozilla.org/en-US/docs/Web/CSS/filter

https://css-tricks.com/using-svg/

--

--