SVG是矢量圖形表示,它的一個強大之處在於path標籤能夠表示任意的矢量形狀,利用好這個path能夠作出不少傳統html/css作不出來的效果。下面舉幾個例子。javascript
這個我在《SVG導航下劃線光標跟隨效果》文後補充介紹了這個實現,最後的效果是這樣的:css
實現代碼以下:html
利用animateMotion結合path作的動畫,具體說明可見上文。前端
以下圖所示,須要實現點到哪一個洲就進入哪一個洲的效果,例如點到非洲就進入非洲:java
咱們能夠用div定一個框蓋在非洲的上面,可是用div的話只能是規則的四方形,沒辦法實現點到非洲大陸的時候才進入,可是大陸的輪廓又是不規則的,因此用傳統html是不能解決這個問題的。可是用SVG的path能夠解決這個問題,方法1是監聽path的點擊事件便可,以下圖所示:算法
由於這個輪廓能夠跟UI要到,他們通常都是用AI/PS等矢量軟件畫出來的,讓他們導一個SVG給你就行了。數組
方法2是能夠調SVG的isPointInFill這個API判斷點擊的點是否在Path的fill區域裏面,這個也能夠實現,可是相對於方法1來講比較麻煩,由於還須要把鼠標的位置轉換爲svg視圖的位置。bash
在第1點沿着路徑的動畫是自動的過程,有沒有辦法讓用戶本身拖拽過去呢,實現以下效果:svg
這種的場景有音量控制等須要有百分比控制的。能夠先用一個SVG的在線工具畫出一個這樣的圖形:工具
就能夠拿到SVG的代碼:
<svg class="volumn-controller" width="580" height="400" xmlns="http://www.w3.org/2000/svg"> <path class="volumn-path" stroke="#000" d="m100,247c93,-128 284,-129 388,6" opacity="0.5" stroke-width="1" fill="#fff"/> <circle class="drag-button" r="12" cy="247" cx="100" stroke-width="1" stroke="#000" fill="#fff"/> </g> </svg>複製代碼
這裏比較關鍵的是path標籤裏的d屬性,d是data的縮寫,定義這個路徑的形狀,它裏面能夠用不少屬性控制形狀的變化,以下圖所示:
爲了實現這個交互,須要動態地改變circle的圓心位置(cx, cy)到路徑上相應的地方。SVG沒有直接提供相關的API,可是它提供了一個能夠間接利用的API叫getPointAtLength,傳遞一個長度參數,以下代碼所示:
let volumnPath = document.querySelector('.volumn-path');
// 輸出path在長度爲100的位置的點座標
console.log(volumnPath.getPointAtLength(100));
// 輸出當前path的總長度
console.log(volumnPath.getTotalLength());複製代碼
控制檯輸出:
把circle的cx/cy改爲上面的x/y座標,圓圈就會跑到對應的位置去了:
這裏的問題在於這個API傳遞的length參數是相對於曲線長度的,可是鼠標移動的位置是線性的,沒辦法直接知道當前鼠標在曲線上距離起始位置多少。
因此須要算一下,在這個場景裏面咱們能夠取鼠標的x座標在曲線上對應的位置就能夠了,以下圖示意:
到這裏就有思路了,能夠把這條路徑上每隔一個像素長度就算一下它的座標在哪裏,而後存在一個數組裏面。因爲鼠標移動的時候x座標是知道的,就能夠查一下在這個數組裏面相應x座標的y座標是多少,就能獲得想要的圓心位置了。
因此先計算一下,保存到一個數組:
let $volumnController = document.querySelector('.volumn-controller'),
$volumnPath = $volumnController.querySelector('.volumn-path');
// 獲得當前路徑的總長度
let pathTotalLength = $volumnPath.getTotalLength() >> 0;
let points = [];
// 起始位置爲長度爲0的位置
let startX = Math.round($volumnPath.getPointAtLength(0).x);
// 每隔一個像素距離就保存一下路徑上點的座標
for (let i = 0; i < pathTotalLength; i++) {
let p = $volumnPath.getPointAtLength(i);
// 保存的座標用四捨五入,能夠平衡偏差
points[Math.round(p.x) - startX] = Math.round(p.y);
}複製代碼
這裏用一個p0ints數組來保存,它的索引index就爲x座標,值爲y座標。在這個例子裏面,總長度爲451.5px,獲得的points數組長度爲388. 你能夠隔0.5px長度就保存一個座標,不過在這個例子裏面1px就夠了。
而後監聽鼠標事件,獲得x座標,查詢y座標,動態地改變circle的圓心位置,以下代碼所示:
let $dragButton = $volumnController.querySelector('.drag-button'),
// 獲得起始位置相對當前視窗的位置,至關於jQuery.fn.offset
dragButtonPos = $dragButton.getBoundingClientRect();
function movePoint (event) {
// 當前鼠標的位置減去圓心起始位置就獲得移位誤差,12是半徑值,這裏先直接寫死
let diffX = event.clientX - Math.round(dragButtonPos.left + 12);
// 須要作個邊界判斷
diffX < 0 && (diffX = 0);
diffX >= points.length && (diffX = points.length - 1);
// startX是在上面的代碼獲得的長度爲0的位置
$dragButton.setAttribute('cx', diffX + startX);
// 使用points數組獲得y座標
$dragButton.setAttribute('cy', points[diffX]);
}
$dragButton.addEventListener('mousedown', function (event) {
document.addEventListener('mousemove', movePoint);
});
document.addEventListener('mouseup', function () {
document.removeEventListener('mousemove', movePoint);
});複製代碼
這個實現的代碼也是比較簡單,須要注意的地方是起始位置的選取,這裏有兩個座標系,一個是相對頁面的視窗的,它的原點(0, 0)座標點是當前頁面可視區域(client)的左上角,第二個座標系是SVG的座標系,它的原點(0, 0)位置是SVG畫布的左上角,以下圖所示:
鼠標的位置是相對於視圖client的,因此須要獲得圓圈在client的位置,能夠經過原生的getBoundingClient獲取,而後用鼠標的clientX減掉圓圈的clientX就獲得正確的位移誤差diff了,這個diff值加上圓圏的在svg座標的起始位置就能獲得svg裏的x座標了,而後去查一下points數組就能獲得y座標,而後去設置circle的cx/cy值。
這個的實現已經算是十分簡單的,大概30行代碼。須要注意的是若是svg縮放了,那麼座標也要相應比例地改一下。因此最好是不要縮放,1:1顯示就簡單多了。
若是要顯示具體的音量值呢?這個也好辦,只須要在第一步保存點座標的時候把在路徑上的長度也保存下來就行了,最後效果以下:
一個完整的demo:svg-path-drag.html
若是路徑比較複雜怎麼辦呢,一個x座標可能會對應兩個點,以下圖所示:
這個也是有辦法的,計算的方法相似,也是須要把路徑上全部每隔1px的點座標都取出來,而後計算一下鼠標的位置距離哪一個點的座標最接近,而後就取那個點就行了。固然在判斷哪一個點最優時,算法須要優化,不能直接一個for循環,具體可見這個codepen。
路徑結合關鍵幀能夠作出一些有趣的效果,如這個codepen的示例:
它的實現是hover的時候改變path的d值,而後作d的transition動畫,以下代碼:
<svg viewBox="0 0 10 10" class="svg-1"> <path d="M2,2 L8,8" /> </svg> <style> .svg-1:hover path { d: path("M8,2 L2,8"); } path { transition: d 0.5s linear; } </style>複製代碼
這種變形過渡動畫是有條件的,就是它的路徑數據格式是要一致的,有多少個M/L/C屬性都要保持一致,不然沒法作變形動畫。
使用CSS一般只能用border-radius作一些圓角的遮罩,即用border-radius結合overflow: hidden實現,可是使用clip-path + svg的路徑可以作出任意形狀遮罩,以下作一個心形的:
以下代碼所示:
<div style="width:200px;height:200px"> <img src="photo.png" alt style="width:100%"> </div> <style> img { clip-path: url("#heart"); } </style>複製代碼
style裏面的id: #heart是指向了一個SVG的的clipPath,以下圖所示:
<svg xmlns="http://www.w3.org/2000/svg" width="0" height="0"> <clipPath id="heart" clipPathUnits="objectBoundingBox"> <path transform="scale(0.0081967, 0.0101010)" d="m61.18795,24.08746c24.91828,-57.29309 122.5489,0 0,73.66254c-122.5489,-73.66254 -24.91828,-130.95562 0,-73.66254z"/> </clipPath> </svg>複製代碼
爲了讓這個path恰好能撐起div容器寬度的100%,須要設置:
clipPathUnits="objectBoundingBox"複製代碼
這樣會致使d屬性裏面的單位變成比例的0到1,因此須要把它縮小一下,本來的width是122,height是99,須要須要scale的值爲(1 / 122, 1 / 99)。這樣就達到100%佔滿的目的,若是一開始d屬性座標比例就是0到1,就不用這麼搞了。
另外clip-path使用svg的path不支持變形動畫。
本篇介紹了使用svg路徑path作的幾種效果:作一個路徑動畫、不規則形狀的點擊、沿着路徑拖拽、路徑的變形動畫以及和clip-path作一些遮罩效果。能夠說svg的path效果仍是很強大的,當你有些效果用html/css沒法實現的時候,不妨往svg的方向思考。
【號外】《高效前端》已經第二次印刷了,據說你還沒買