Translate

2020年11月27日 星期五

SVG 的文字

以前自己開發繪圖函式庫時,字都是自己畫,一開始是抓倚天中文的字形檔,後來用自己的方式產生自己的字形檔,並加上 alpha 來做反鋸齒,當時還為了開發泰國版本,自行設計泰文輸入法,雖然可以使用 truetype 字形,但是當時硬體規格太差,為了速度,還是要自己寫效率比較高,現在看到 SVG 文字相關的定義,真的覺得當初自己所想到的功能太陽春,SVG 的規劃要完整多了。

text 的參數如下:

x,y                座標
dx,dy              偏移
rotate             旋轉
lengthAdjust       spacing (預設) 字與字之間以空白做間隔
                   spacingAndGlyphs  字與字之間以變形填滿
textLength         字串長度
text-anchor        start , middle , end 文字定位  

以下參數也可以做為 CSS 的參數

dominant-baseline  auto (預設)
                   text-bottom
                   alphabetic
                   ideographic
                   middle
                   central
                   mathematical
                   hanging
                   text-top
writing-mode       horizontal-tb
                   vertical-rl
                   vertical-lr  

先來一個簡單範例


<svg width="320" height="200" viewBox="0 0 420 200" style="background-color:#ffa">
<text x="10" y="160" stroke-width="10" stroke="#f00" fill="#0ff" style="font-size:180px;font-weight:900">1a</text>
<text x="210" y="160" stroke-linejoin="round" stroke-width="10" stroke="#f00" fill="#0ff" style="font-size:180px;font-weight:900">1a</text>
</svg>
1a 1a

可以發現,SVG 的字完全就是用畫的,像多邊形一樣,所以右邊的文字有加上 stroke-linejoin="round" 在角落的地方就成了圓角

文字處理另一個麻煩的就是基準線,這裡使用的是 dominant-baseline ,可以當作參數,也可以當作 CSS 使用

auto 測試1a

CSS 的文字有個很特別的設計,利用類似陣列的方式為每一個字設定屬性


<svg width="300" height="100" style="background-color:#ffa">
<text x="10,20,40,80,150" y="60,70,50" fill="#000" style="font-size:32px;">abcde</text>
</svg>
abcde

也可以用 dx 和 dy 做偏移


<svg width="300" height="100" style="background-color:#ffa">
<text x="10" y="60" dx="0,10,20,30,40" dy="0,10,20,-40" fill="#000" style="font-size:32px;">abcde</text>
</svg>
abcde

可以用 rotate 旋轉, 而旋轉預設的中心點在左下角


<svg width="300" height="100" style="background-color:#ffa">
<text x="10" y="60" rotate="0,30,60,90,-40,0,0,90,-90" fill="#000" style="font-size:32px;">abcde ddd</text>
</svg>
abcde ddd

textLength 和 lengthAdjust 搭配,設定字串長度後,看要用空白還是變形的方式填空間


<svg width="320" height="200" viewBox="0 0 420 200" style="background-color:#ffa">
<text x="10" y="40" textLength="300" lengthAdjust="spacing" fill="#000" style="font-size:32px;">abcde</text>
<text x="10" y="80" textLength="300" lengthAdjust="spacingAndGlyphs" fill="#000" style="font-size:32px;">abcde</text>
<text x="10" y="120" textLength="60" lengthAdjust="spacingAndGlyphs" fill="#000" style="font-size:32px;">abcde</text>
<text x="360" y="20" textLength="140" lengthAdjust="spacingAndGlyphs" fill="#000" style="font-size:32px;writing-mode:vertical-lr">直寫測試123</text>
</svg>
abcde abcde abcde 直寫測試123

tspan

當一段文字想要有不同設定時,可以用 tspan ,就如同 css 的 span 一樣,搭配 dominant-baseline 就可以做到上標字和下標字的效果,

*經實測結果 Firefox 會不正常顯示,以下正確結果應該顯示為 X₂+Y²=Z⁽²ⁿ⁺¹⁾


<style>
.a112707_central
{
  dominant-baseline:central;
}
.a112707_up
{
  font-size:0.6em;
  dominant-baseline:auto;
}
.a112707_down
{
  font-size:0.6em;
  dominant-baseline:hanging;
}
</style>
<svg width="320" height="200" style="background-color:#ffa">
<text x="10" y="40" fill="#000" style="font-size:32px;">abc<tspan fill="#f00">123</tspan>def<tspan style="font-size:48px">def</tspan>ghi</text>
<text x="10" y="100" fill="#000" style="font-size:32px;" class="a112707_central">
X
<tspan class="a112707_down">2</tspan>
+Y
<tspan class="a112707_up">2</tspan>
=Z
<tspan class="a112707_up">(2n+1)</tspan>
</text>
</svg>
abc123defdefghi X 2 +Y 2 =Z (2n+1)

如果要完全相容的上標和下標字,似忽只有利用 dy 來實作,但是這個缺點就是,沒辦法單純用 css 來完成,而且 <tspan> 的定義很奇怪,一但 dy 改變了,就是改變了,並不會在 </tspan> 時變回來,所以在使用上標字(dy 減去某個值) ,必須在下一個顯示的字把 dy 的值加回來。


<style>
.a112708_normal
{
  font-size:1em;
}
.a112708_up
{
  font-size:0.6em;
}
.a112708_down
{
  font-size:0.6em;
}
</style>
<svg width="320" height="100" style="background-color:#ffa">
<text x="10" y="60" fill="#000" style="font-size:32px;">
<tspan class="a112708_normal">X</tspan>
<tspan class="a112708_down">2</tspan>
<tspan class="a112708_normal">+Y</tspan>
<tspan class="a112708_up" dy="-1em">2</tspan>
<tspan class="a112708_normal" dy="0.6em">=Z</tspan>
<tspan class="a112708_up" dy="-1em">(2n+1)</tspan>
</text>
</svg>
X 2 +Y 2 =Z (2n+1)

為了處理上標和下標問題,只好利用 javascript 來協助,加上 svgsup 和 svgsub 兩個 class ,由 javascript 來處理 dy 的問題,程式碼很簡單,效果也不錯


<style>
.svgsup,.svgsub
{
  font-size:0.6em;
}
</style>
<svg width="320" height="100" style="background-color:#ffa">
<text x="10" y="60" fill="#000" style="font-size:32px;">
<tspan>X</tspan>
<tspan class="svgsub">2</tspan>
<tspan>+Y</tspan>
<tspan class="svgsup">2</tspan>
<tspan>=Z</tspan>
<tspan class="svgsup">(2n+1)</tspan>
</text>
</svg>

<script>
$(function()
{
  $(".svgsup").attr("dy","-1em");
  $(".svgsup + tspan").attr("dy","0.6em");
})
</script>
X 2 +Y 2 =Z (2n+1)

textPath

SVG 有個有趣的東西叫 textPath ,文字可以隨著路徑跑,這個功能當初我在設計網頁上的向量地圖時曾經用來顯示路名

當初在寫導航程式顯示路名時,字隨路徑走是用比較偷懶的方式,計算出每個字的座標,但是字不會旋轉,隨著現在硬體規格越來越強,加上 SVG 有這麼方便的指令,要開發 2D 地圖真的簡單多了。


<svg width="320" height="200" viewBox="0 0 400 200" style="background-color:#ffa">
<defs>
  <path id="a112709_p1" d="M20,100 Q100,0 200,100 Q300,200 380,100"></path>
</defs>
<use xlink:href="#a112709_p1" fill="none" stroke="#fa0" stroke-width="2"></use>
<text fill="#000" style="font-size:24px;">
  <textPath href="#a112709_p1">文字可以隨著路徑跑
  </textPath>
</text>
</svg>
<br><br>
<svg width="320" height="200" viewBox="0 0 400 200" style="background-color:#ffa">
<use xlink:href="#a112709_p1" fill="none" stroke="#fa0" stroke-width="2"></use>
<text fill="#000" style="font-size:24px;">
  <textPath href="#a112709_p1" startOffset="100">startOffset="100"
  </textPath>
</text>
</svg>
<br><br>
<svg width="320" height="200" viewBox="0 0 400 200" style="background-color:#ffa">
<use xlink:href="#a112709_p1" fill="none" stroke="#fa0" stroke-width="2"></use>
<text fill="#000" style="font-size:24px;">
  <textPath href="#a112709_p1">可以使用<tspan fill="#f0f" dy="-20" style="font-size:32px">tspan</tspan><tspan dy="20" fill="#00f">來做效果</tspan>
  </textPath>
</text>
</svg>
<br><br><font color="#f00"><b>這個設定在 iPhone 及 safari 沒用</b></font><br>
<svg width="320" height="200" viewBox="0 0 400 200" style="background-color:#ffa">
<use xlink:href="#a112709_p1" fill="none" stroke="#fa0" stroke-width="2"></use>
<text fill="#000" style="font-size:24px;" dominant-baseline="central">
  <textPath href="#a112709_p1">dominant-baseline="central"
  </textPath>
</text>
</svg>
<br><br>
<svg width="320" height="200" viewBox="0 0 400 200" style="background-color:#ffa">
<use xlink:href="#a112709_p1" fill="none" stroke="#fa0" stroke-width="2"></use>
<text fill="#000" style="font-size:24px;writing-mode:vertical-lr;">
  <textPath href="#a112709_p1">直式書寫 writing-mode:vertical-lr;
  </textPath>
</text>
</svg>

文字可以隨著路徑跑

startOffset="100"

可以使用tspan來做效果

這個設定在 iPhone 及 safari 沒用
dominant-baseline="central"

直式書寫 writing-mode:vertical-lr;

文字可以隨著路徑跑,那麼是不是可以取得路徑長度後,計算每個字的間距,讓字平均分布呢 ?

方法如下 :

  let pp  = document.getElementById("[path 的 ID]");
  let len = pp.getTotalLength(); //取得路徑總長度
  let xy  = pp.getPointAtLength(500); //取得距離 500 的點位 , return {x:n,y:m}
  let box = pp.getBBox(); //取得路徑範圍 , return {x:n,y:n,width:n,height:n}

取得長度後就可以計算平均分布的位置


<svg width="300" height="300" style="background-color:#ffa">
<defs>
  <path id="a112710_p1" d="M30,150 a1,1,0,0,1,240,0 a1,1,0,0,1,-240,0"></path>
</defs>
<use xlink:href="#a112710_p1" fill="none" stroke="#fa0" stroke-width="2"></use>
<text fill="#000" style="font-size:24px;">
  <textPath href="#a112710_p1">
    <tspan id="a112710_txt">文字可以隨著路徑跑喲</tspan>
  </textPath>
</text>
</svg>
<script>
$(function()
{
  let pp = document.getElementById("a112710_p1");
  let len = pp.getTotalLength();
  let txt=$("#a112710_txt");
  let txtlen=txt.html().length;
  let i;
  let hh="";
  let j=len/txtlen;
  for (i=0;i<txtlen;i++)
  {
    hh+=j*i+" ";
  }
  txt.attr("x",hh);  
})
</script>  
文字可以隨著路徑跑喲

2020年11月25日 星期三

SVG 材質貼圖

對於 2D 的圖形,材質貼圖相當實用,可以讓畫面更美觀,當初自己在設計繪圖函式庫時也花了不少時間在處理貼圖的部份,而現在 SVG 的貼圖做得更加完善

pattern 參數 :

viewBox                "x,y,width,height"
width                  貼圖範圍
height

patternUnits           objectBoundingBox (預設)
                       userSpaceOnUse 
patternTransform       接 transform 參數

preserveAspectRatio    預設 xMidYMid meet

先來一個最常用的範例

用 pattern 建立一個材質,使用 fill="url(#ID)" 來使用這個材質


<svg xmlns="http://www.w3.org/2000/svg" width="280" height="200" style="background-color:#aef">
  <defs>
    <pattern id="a112500_p1" width="30" height="30" patternUnits="userSpaceOnUse">
      <circle cx="10" cy="10" r="10" fill="#a5f"></circle>
    </pattern>
  </defs>
  <path d="M10,100 L140,10 Q270,0,270,100 Q140,90,140,190z" stroke="#000" fill="url(#a112500_p1)"></path>
</svg>

詳細說明如下 :

圖1

當設定viewBox 以及 width 和 height 必須用 % (百分比) 表示時,材質大小會跟著縮放

viewBox="0,0,40,40" width="20%" height="20%" 

圖2

圖1 加上 preserveAspectRatio="none" 後,材質比例也跟著變形

viewBox="0,0,40,40" width="20%" height="20%"
preserveAspectRatio="none"

圖3

設定 patternUnits ="userSpaceOnUse" 後,材質變成以整個 svg 為底,大小位置也固定了

viewBox="0,0,40,40" width="20%" height="20%"
patternUnits ="userSpaceOnUse"

圖4

不設定 viewBox 只設定 patternUnits ="userSpaceOnUse" 時,width 和 height 如果用數字就是材質大小 ,如果是百分比 (%)就是百分比

width="40" height="40" patternUnits ="userSpaceOnUse"

2020年11月22日 星期日

SVG 的 marker

SVG 對於線段有個有用的設定是 marker , 可以用來設定線段的起點、中間點,和終點的圖示

marker 的參數 :

viewBox      設定 icon 大小,格式為 "x y 寬 高"
refX         設定 icon 的原點座標
refY
markerWidth  設定 icon 要縮放的大小
markerHeight
orient       設定 icon 旋轉角度,設 auto 的話會自動隨者線段旋轉,auto-start-reverse 則起點會反過來

使用時只要加上 marker-start(#ID) , marker-mid(#ID) , marker-end(#ID) 即可


<svg style="background-color:#ffa" width="300" height="140">
<defs>
  <marker id="a11221_m01" viewBox="-10 -10 20 20" refX="0" refY="0" markerWidth="15" markerHeight="15">
    <circle cx="0" cy="0" r="10" fill="#f008"></circle>
  </marker>
  <marker id="a11221_m02" viewBox="0 0 20 20" refX="10" refY="10" markerWidth="15" markerHeight="15">
    <rect x="0" y="0" width="20" height="20" fill="#0f08"></rect>
  </marker>
  <marker id="a11221_m03" viewBox="0 0 20 20" refX="10" refY="10" markerWidth="15" markerHeight="15" orient="auto">
    <polygon points="0,0 20,10 0,20" fill="#00f8"></polygon>
  </marker>
</defs>
  <path id="a11221_p01" d="M20,20 80,120 140,20 200,120 260,20" stroke="#000" stroke-width="1" fill="none" marker-start="url(#a11221_m01)" marker-mid="url(#a11221_m02)" marker-end="url(#a11221_m03)"></path>
</svg>



<style>
.a11221_bb
{
  display:inline-block;
  width:260px;
  padding:4px;
}
</style>

<div class="a11221_bb">圖1:marker 的箭頭,一個長寬都是20方向向右的三角形<br>
<svg style="background-color:#ffa" width="260" height="140">
  <defs>
  <path id="a11221_p1" d="M30,20 L130,20 L230,20 L230,100 L100,60" fill="none" stroke="#000"></path>
  </defs>
  <path id="a11221_p2" d="M0,0 L20,10 L0,20z" fill="#f008"></path>
</svg>
</div>

<div class="a11221_bb">圖2:由 markerWidth 和 markerHeight 把箭頭設為 15x15<br>
<svg style="background-color:#ffa" width="260" height="140">
<defs>
  <marker id="a11221_m1" viewBox="0 0 20 20" refX="0" refY="10" markerWidth="15" markerHeight="15">
    <use xlink:href="#a11221_p2"></use>
  </marker>
</defs>
  <use xlink:href="#a11221_p1" stroke-width="1" marker-start="url(#a11221_m1)" marker-mid="url(#a11221_m1)" marker-end="url(#a11221_m1)"></use>
</svg>
</div>

<div class="a11221_bb">圖3:加上 orient="45",箭頭角度向右旋轉 45 度<br>
<svg style="background-color:#ffa" width="260" height="140">
<defs>
  <marker id="a11221_m3" viewBox="0 0 20 20" refX="0" refY="10" markerWidth="15" markerHeight="15" orient="45">
    <use xlink:href="#a11221_p2"></use>
  </marker>
</defs>
  <use xlink:href="#a11221_p1" stroke-width="1" marker-start="url(#a11221_m3)" marker-mid="url(#a11221_m3)" marker-end="url(#a11221_m3)"></use>
</svg>
</div>

<div class="a11221_bb">圖4:加上 orient="auto" 後,箭頭角度會隨著線旋轉<br>
<svg style="background-color:#ffa" width="260" height="140">
<defs>
  <marker id="a11221_m4" viewBox="0 0 20 20" refX="0" refY="10" markerWidth="15" markerHeight="15" orient="auto">
    <use xlink:href="#a11221_p2"></use>
  </marker>
</defs>
  <use xlink:href="#a11221_p1" stroke-width="1" marker-start="url(#a11221_m4)" marker-mid="url(#a11221_m4)" marker-end="url(#a11221_m4)"></use>
</svg>
</div>

<div class="a11221_bb">圖5:加上 orient="auto-start-reverse" 後,起點的箭頭角度會旋轉 180 度<br>
<svg style="background-color:#ffa" width="260" height="140">
<defs>
  <marker id="a11221_m5" viewBox="0 0 20 20" refX="0" refY="10" markerWidth="15" markerHeight="15" orient="auto-start-reverse">
    <use xlink:href="#a11221_p2"></use>
  </marker>
</defs>
  <use xlink:href="#a11221_p1" stroke-width="1" marker-start="url(#a11221_m5)" marker-mid="url(#a11221_m5)" marker-end="url(#a11221_m5)"></use>
</svg>
</div>

<div class="a11221_bb">圖6:當線條寬度設為 2 時 (stroke-width="2") 箭頭也隨著增大兩倍<br>
<svg style="background-color:#ffa" width="260" height="140">
<defs>
  <marker id="a11221_m6" viewBox="0 0 20 20" refX="0" refY="10" markerWidth="15" markerHeight="15" orient="auto-start-reverse">
    <use xlink:href="#a11221_p2"></use>
  </marker>
</defs>
  <use xlink:href="#a11221_p1" stroke-width="2" marker-start="url(#a11221_m6)" marker-mid="url(#a11221_m6)" marker-end="url(#a11221_m6)"></use>
</svg>
</div>

圖1:marker 的箭頭,一個長寬都是20方向向右的三角形
圖2:由 markerWidth 和 markerHeight 把箭頭設為 15x15
圖3:加上 orient="45",箭頭角度向右旋轉 45 度
圖4:加上 orient="auto" 後,箭頭角度會隨著線旋轉
圖5:加上 orient="auto-start-reverse" 後,起點的箭頭角度會旋轉 180 度
圖6:當線條寬度設為 2 時 (stroke-width="2") 箭頭也隨著增大兩倍

2020年11月20日 星期五

SVG 的 mask

SVG 的 mask 和 CSS 有一點不同,用的是黑白來代表透明度,白色是不透明,黑色是透明,如以下範例,圖1是 mask 用的圖,圖2是 rect 套用 mask 後,由黃色部份可以看到,中間的那個心是透明的。

clip-path 是直接裁切,mask 似忽可以取代 clip-path,而且還可以加上透明度


<div style="display:inline-block">圖1<br>
<svg width="200" height="200" style="background-color:#000">
<defs>
  <path id="a11201_p1" d="M0,50 a1,1,0,0,1,100,0 a1,1,0,0,1,100,0 C200,110,150,150,100,180 C50,150,0,110,0,50Z"></path>
</defs>
  <use xlink:href="#a11201_p1" fill="#fff"></use>
  <use xlink:href="#a11201_p1" fill="#000" transform="translate(30,30) scale(0.7)"></use>
  <use xlink:href="#a11201_p1" fill="#888" transform="translate(50,50) scale(0.5)"></use>
</svg>
</div>

<div style="display:inline-block">圖2<br>
<svg width="200" height="200">
<mask id="a11201_m1">
  <use xlink:href="#a11201_p1" fill="#fff"></use>
  <use xlink:href="#a11201_p1" fill="#000" transform="translate(30,30) scale(0.7)"></use>
  <use xlink:href="#a11201_p1" fill="#888" transform="translate(50,50) scale(0.5)"></use>
</mask>  
<rect x="0" y="0" width="100" height="200" fill="#ff0"></rect>
<rect x="0" y="0" width="200" height="200" mask="url(#a11201_m1)" fill="#00f"></rect>
</svg>
</div>

圖1
圖2

mask 的有趣範例

直接把圖檔當成 mask ,可以把彩色圖片變成黑白


<svg width="200" height="200" style="background-color:#000">
<mask id="a11202_aa">
  <image href="https://yiharng.github.io/bird.jpg" width="200" height="200" preserveAspectRatio="none"></image>
</mask>
<rect x="0" y="0" width="200" height="200" mask="url(#a11202_aa)" fill="#fff"></rect>
</svg>

圖片套用文字當 mask,可以做出不錯的文字特效


<svg width="200" height="100">
<mask id="a11203_bb">
  <text x="22" y="62" font-size="60" stroke-width="9" fill="#777" stroke="#777">ASDF</text>
  <text x="20" y="60" font-size="60" stroke-width="4" fill="#fff" stroke="#fff">ASDF</text>
</mask>
  <image href="https://yiharng.github.io/bird.jpg" width="200" height="100" preserveAspectRatio="none" mask="url(#a11203_bb)"></image>
</svg>
ASDF ASDF

2020年11月18日 星期三

CSS 狀態改變動畫

之前寫了一篇 CSS 動畫 時,忘了應該先寫這個狀態變化的動畫,大部份的應用都是用在滑鼠移到上面時的用動畫方式改變狀態,實際上,這個功能對於所有狀態變化都可以變成動畫

參數說明:

transition                  [動畫持續時間] [delay 多久開始] [timing-function]
transition-delay            [delay 多久開始]
transition-duration         [動畫持續時間]
transition-property         [指定改變的屬性]
transition-timing-function  [動畫 timing]

我個人偏好不使用 transition-property ,這樣可以更自由的使用這個功能

首先先來一個最簡單的範例:

#a11181_div:hover 裡頭加了 transition: 1s 後 ,滑鼠移過去時,顏色會慢慢的變成黃色,同時長度從 100px 慢慢變成 200px, 當滑鼠移開時,因為在 #a11181_div 加上了 transition: 0.2s; ,會以動畫方式很快的恢復原狀,如果沒有加上 transition ,狀態會瞬間改變,不會有動畫效果,另外還有加上 transition-delay , 所以會等 0.5 秒後才開始動畫

而下面的 [SET] 和 [UNSET] 是利用 javascript 去做設定,結果一樣會以動畫顯示, 當用 javascript 設定時,是直接寫在元件上,所以 hover 改變 width 就沒作用了,而 [CSS SET] 和 [CSS UNSET] 的是直接修改本來的 CSS 設定,所以不影響 hover 。


<style id="a11181_css">
#a11181_div
{
  width: 100px;
  height: 100px;
  background-color:#f00;
  transition: 0.2s;
  transition-delay:0.5s;
  transition-timing-function: linear; 
}
#a11181_div:hover
{
  background-color:#ff0;
  width:200px;
  transition: 1s cubic-bezier(0.6, -0.6, 0.1, 1.6);
}
#a11181_set button
{
  font-size:1em;
}
</style>

<div id="a11181_div">
</div>
<br>
<div id="a11181_set">
<button id="a11181_bb">SET</button>
<button id="a11181_bb1">UNSET</button>
<button id="a11181_bb2">CSS SET</button>
<button id="a11181_bb3">CSS UNSET</button>
</div>
<script>
$("#a11181_bb").on("click",function()
{
  $("#a11181_div").css("width","300px");
});
$("#a11181_bb1").on("click",function()
{
  $("#a11181_div").css("width","");
});
$("#a11181_bb2").on("click",function()
{ 
  var rc=cssjson("#a11181_css"); 
  rc.set("#a11181_div",
  {
    width:"300px"
  });
});
$("#a11181_bb3").on("click",function()
{ 
  var rc=cssjson("#a11181_css"); 
  rc.set("#a11181_div",
  {
    width:"100px"
  });
});
</script>  


2020年11月14日 星期六

戲說線段

線段有很多屬性,無意間看到有人利用這些屬性做動畫相當有意思,決定先來整理線段的屬性

stroke-linecap

圖1:
polyline
圖2:(預設)
stroke-linecap="butt"
圖3:
stroke-linecap="round"
圖4:
stroke-linecap="square"

stroke-linejoin

圖1:(預設)
stroke-linejoin="miter"
圖2:
stroke-linejoin="round"
圖3:
stroke-linejoin="bevel"
圖4:(可能無作用)
stroke-linejoin="miter-clip"
圖5:(可能無作用)
stroke-linejoin="arcs"

stroke-dasharray

這是一個陣列型態,奇數為要顯示的點數,偶數為不顯示的點數
[10,20] 等於 [10,20,10,20,....] 換言之如果陣列是基數 [10,20,30] 等於 [10,20,30,10,20,30,....]

圖1:
stroke-dasharray="10"
圖2:
stroke-dasharray="10,20"
圖3:
stroke-dasharray="10,20,30"

stroke-dashoffset

虛線起始的點數

圖1:
stroke-dashoffset="0"
圖2:
stroke-dashoffset="10"
圖3:
stroke-linecap="round"

利用 stroke-dashoffset 來產生動畫


<style>
@keyframes a11142_k1
{
  100%
  {
    stroke-dashoffset:502.655;
  }
}
#a11142_circle
{
  animation:a11142_k1 3s infinite linear
}
</style>

<svg xmlns="http://www.w3.org/2000/svg" style="background-color:#ffa" width="200" height="200">

<circle id="a11142_circle" cx="100" cy="100" r="80" stroke="#fa0" fill="#0000" stroke-width="20" stroke-dasharray="20 80.53" stroke-dashoffset="0" stroke-linecap="round"></circle>

</svg>

搭配 path 可以做出相當有意思的動畫


<style>
@keyframes a11143_k1
{
  100%
  {
    stroke-dashoffset:464;
  }
}
#a11143_path
{
  animation:a11143_k1 3s infinite linear
}
</style>

<svg xmlns="http://www.w3.org/2000/svg" style="background-color:#ffa" width="200" height="100">

<defs>
<path id="a11143_def" d="M10,50 C20,10,90,10,100,50 C120,90,180,90,190,50 C180,10,120,10,100,50 C90,90,20,90,10,50Z"></path>
</defs>

<use xlink:href="#a11143_def" stroke="#fa0" fill="#0000" stroke-width="2" stroke-linecap="round"></use>

<use xlink:href="#a11143_def" id="a11143_path" stroke="#f00" fill="#0000" stroke-width="10" stroke-dasharray="15,449" stroke-dashoffset="0" stroke-linecap="square"></use>

</svg>

也可以用在滑鼠移動時的動態效果


<style>
#a11144_svg:hover #a11144_line1
{
    stroke-dashoffset:-125;
    transition:0.4s ease-in-out;
}
#a11144_svg:hover #a11144_line2
{
    stroke-dashoffset:-103;
    transition:0.4s ease-in-out;
}
#a11144_line1
{
  stroke-width:2;
  fill:none;
  stroke:#f00;
  stroke-dasharray:40 135;
  stroke-dashoffset:0;
  transition:1s ease-in-out;
}
#a11144_line2
{
  stroke-width:2;
  fill:none;
  stroke:#00f;
  stroke-dasharray:40 115;
  stroke-dashoffset:0;
  transition:1s ease-in-out;
}
</style>

<svg id="a11144_svg" viewBox="10 15 60 60" xmlns="http://www.w3.org/2000/svg" width="120" height="120">

<path id="a11144_line1" d="M20,40 l40,0 c0,30,-40,30,-40,-15 l40,40"></path>
<path id="a11144_line2" d="M60,50 l-40,0 c0,-30,40,-30,40,-25 l-40,40"></path>

</svg>

2020年11月13日 星期五

Inline SVG

SVG 本身被視為圖檔,那麼能不能畫好一個 SVG 後,即時拿來用在 html 中的 img 或 background-image 呢 ? 方法還是有的,只是必須透過 javascript 才行

主要用的技巧是 data:image/svg+xml;base64,[data]
那麼如果不用 base64 呢 ? 也可以用 data:image/svg+xml;charset=UTF-8,[data]data:image/svg+xml;utf8,[data] 而這個 [data] 必須用 escape 轉換

在實測過程中發現,平常使用 SVG 時並沒有加上完整檔頭,以致於花了些時間才找到圖片出不來的原因

SVG 檔頭宣告 : xmlns="http://www.w3.org/2000/svg"

  <svg xmlns="http://www.w3.org/2000/svg"></svg>  

範例如下 :


<style>
#a1113a_iddiv
{
  width:300px;
  height:200px;
  background-image:url(bomb.png);
  background-size:50px;
}
</style>  
<svg id="a1113a_idsvg" xmlns="http://www.w3.org/2000/svg" style="background-color:#ff0" width="200" height="200">
<path d="M30,200 L100,0 L170,200 L0,70 L200,70Z" fill="#00f"></path>
</svg>

<img id="a1113a_idimg" width="100/">
<img id="a1113a_idimg1" width="100/">
<br>
<div id="a1113a_iddiv"></div>

<script>
$(function()
{
  var kk=document.querySelector("#a1113a_idsvg").outerHTML;
  var k8="data:image/svg+xml;base64,"+btoa(kk);
  var kt="data:image/svg+xml;charset=UTF-8,"+escape(kk);

  $("#a1113a_idimg").attr("src",k8);
  $("#a1113a_idimg1").attr("src",kt);
  $("#a1113a_idsvg path").attr("fill","#e70");

  kk=document.querySelector("#a1113a_idsvg").outerHTML;
  k8="data:image/svg+xml;base64,"+btoa(kk);

  $("#a1113a_iddiv").css("background-image","url("+k8+")");
});
</script>


SVG 的 clipPath

SVG 有超強的 path 可以設定路徑,也可以用這個路徑來做一些應用,最實用的功能之一就是裁切, 而且在 SVG 的 defs 中定義的 clipPath 可以直接在 CSS 裡面用,在 MAC 的 safari 以及 iphone 上要使用 -webkit-clip-path ,但是也不完全正常,使用方式在 style 裡頭加上 clip-path:url(#ID);-webkit-clip-path:url(#ID); 即可

如以下範例,圖四是使用 img + clip-path 做裁切,因為在 clipPath 使用了多個 path ,在 safari 以及 iphone 上無法正常顯示,而圖六是用 svg 的 image 顯示圖片,並且使用 clip-path 裁切,在所有瀏覽器都可以正常顯示。

範例如下:



<svg viewBox="-1,-1,202,180" style="width:0;height:0">
  <defs>
  <clipPath id="a11131_cp1">
    <path id="a11131_p1" d="M0,50 a1,1,0,0,1,100,0 a1,1,0,0,1,100,0 C200,110,150,150,100,180 C50,150,0,110,0,50Z"></path>
  </clipPath>
  <clipPath id="a11131_cp2">
    <use xlink:href="#a11131_p1" transform="scale(0.4)"></use>
    <use xlink:href="#a11131_p1" transform="translate(70,50) scale(0.65)"></use>
    <use xlink:href="#a11131_p1" transform="translate(20,120) scale(0.3)"></use>
  </clipPath>
  </defs>
</svg> 
<style>
.a11131_s1
{
  display:inline-block;
}
</style>
<div class="a11131_s1">圖一<br>
<svg style="width:200px;height:200px;background:#ff0">
  <use xlink:href="#a11131_p1" fill="#f00"></use>
</svg>
</div>

<div class="a11131_s1">圖二<br>
<svg style="width:200px;height:200px;background:#ff0">
  <rect x="0" y="0" width="200" height="200" clip-path="url(#a11131_cp1)" fill="#00f">
  </rect>
</svg>
</div>

<div class="a11131_s1">圖三<br>
<img src="https://yiharng.github.io/bird.jpg" style="padding:0px;border:0px;width:200px;height:200px;clip-path:url(#a11131_cp1);-webkit-clip-path:url(#a11131_cp1)">
</div>

<div class="a11131_s1">圖四<br>
<img src="https://yiharng.github.io/bird.jpg" style="padding:0px;border:0px;width:200px;height:200px;clip-path:url(#a11131_cp2);-webkit-clip-path:url(#a11131_cp2)">
</div>  

<div class="a11131_s1">圖五<br>
<svg style="width:200px;height:200px;background:#ff0">
  <rect x="0" y="0" width="200" height="200" clip-path="url(#a11131_cp2)" fill="#00f">
  </rect>
</svg>
</div>

<div class="a11131_s1">圖六<br>
<svg style="width:200px;height:200px;background:#ff0">
  <image href="https://yiharng.github.io/bird.jpg" x="0" y="0" width="200" height="200" preserveAspectRatio="none" clip-path="url(#a11131_cp2)"></image>
</svg>
</div>


圖一
圖二
圖三
圖四
圖五
圖六

clipPath 裡頭的路徑都是 OR 運算,要做 AND 運算的話,可以在 clipPath 再加上 clip-path **(圖三)**

在這個範例中畫了一個星星,通常在繪圖時要挖空重疊的部份用的是 fill-rule="evenodd" ,然而在 clipPath 中無效,必須用 clip-rule="evenodd" 才行

在這邊也發現了一個 chrome 的 bug , clipPath 基本上無法做 XOR 運算,結果圖四在 chrome 卻出現了類似 XOR 的效果,在 firefox 和 safari 沒有問題



<svg id="a11132_idsvg" xmlns="http://www.w3.org/2000/svg" width="0" height="0">
<defs>
<clipPath id="a11132_cp1" clip-rule="evenodd">
  <path id="a11132_p1" d="M30,200 L100,0 L170,200 L0,70 L200,70Z" fill="#00f"></path>
  <circle cx="100" cy="100" r="20"></circle>
</clipPath>
<clipPath id="a11132_cp2">
  <use xlink:href="#a11132_p1"></use>
  <circle cx="100" cy="100" r="60"></circle>
</clipPath>
<clipPath id="a11132_cp3" clip-path="url(#a11132_cp1)">
  <circle cx="100" cy="100" r="60"></circle>
</clipPath>
<clipPath id="a11132_cp4" clip-rule="evenodd">
  <path id="a11132_p1" d="M30,200 L100,0 L170,200 L0,70 L200,70Z" fill="#00f"></path>
  <circle cx="100" cy="100" r="60"></circle>
</clipPath>
</defs>  
</svg>
<div style="display:inline-block">
圖一<br>
<svg width="200" height="200">
<rect x="0" y="0" width="200" height="200" clip-path="url(#a11132_cp1)" fill="#00f"></rect>
</svg>
</div>
<div style="display:inline-block">
圖二<br>
<svg width="200" height="200">
<rect x="0" y="0" width="200" height="200" clip-path="url(#a11132_cp2)" fill="#00f"></rect>
</svg>
</div>
<div style="display:inline-block">
圖三<br>
<svg width="200" height="200">
<rect x="0" y="0" width="200" height="200" clip-path="url(#a11132_cp3)" fill="#00f"></rect>
</svg>
</div>  
<div style="display:inline-block">
圖四<br>
<svg width="200" height="200">
<rect x="0" y="0" width="200" height="200" clip-path="url(#a11132_cp4)" fill="#00f"></rect>
</svg>
</div>  
圖一
圖二
圖三
圖四

2020年11月12日 星期四

SVG 的 defs 以及漸層填色

SVG 的 defs 可以用來定義各種圖形或路徑,在其它 svg 中只要用 <use xlink:href="#ID"/> 即可引用,範例如下

在這個範例中,第一個 svg 是隱藏的,純脆只定義幾個矩形,下面兩個 svg 引用後繪製圖形,在引用時可以設定裡頭的參數,而這裡設定的座標參數是相對座標

defs 的位置似忽沒有限制,範例中的 a11121_rect5 在前面使用,卻在後面才定義,結果一樣可以正常顯示



<svg style="display:none;">
<defs>
  <g id="a11121_g1">
    <rect id="a11121_rect1" width="50" height="50" x="30" y="10" fill="#f00"></rect>
    <rect id="a11121_rect2" width="50" height="50" x="30" y="80" fill="#f00"></rect>
    <rect id="a11121_rect1" width="50" height="50" x="200" y="10" fill="#000"></rect>
    <!--   a11121_rect1 重覆了   -->
    <text x="30" y="40" fill="#fff">x=30</text>
    <text x="200" y="40" fill="#fff">x=200</text>
    <text x="100" y="75" fill="#000">x=100</text>
  </g>
</defs>
</svg>

<svg style="background-color:#afa">
    <use xlink:href="#a11121_g1"></use>
    <!--   a11121_rect1 重覆時,當使用整個 group ,一樣會顯示   -->
    <use xlink:href="#a11121_rect1" x="100"></use>
    <!--  這裡重設 x="100" 結果位置卻是在 130 也就是重設的座標是相對座標  -->
    <text x="130" y="40" fill="#fff">x=130</text>
    <use xlink:href="#a11121_rect5"></use>
</svg>

<svg style="background-color:#aff">
    <use xlink:href="#a11121_rect1"></use>
    <use xlink:href="#a11121_rect1" x="100"></use>
    <!--   a11121_rect1 重覆時,只會取用第一個   -->
    <use xlink:href="#a11121_rect2"></use>
    <use xlink:href="#a11121_rect3" x="100"></use>
    <defs>
        <rect id="a11121_rect5" width="50" height="50" x="200" y="80" fill="#0e0"></rect>
    </defs>
</svg>

<button id="a11121_b1" style="font-size:1em">改顏色</button>
<button id="a11121_b2" style="font-size:1em">新增矩形</button>

<script>
(function()
{
  $("#a11121_b1").on("click",()=>
  {
    $("#a11121_rect2").attr("fill","#00f");
  });

  $("#a11121_b2").on("click",()=>
  {
    $("#a11121_g1").append('<rect id="a11121_rect3" width="50" height="50" x="100" y="80" fill="#f0f"></rect>');

    $("#a11121_g1").html($("#a11121_g1").html());
/* 光 append 一段 html 沒有作用,整個 html 置換才有作用,所以要加這一行 */
  });


})();

</script>
x=30 x=200 x=100 x=130


defs 的應用-漸層填色

defs 的第一個應用是漸層填色,和 CSS 的漸層填色很像,一個線性漸層和一個圓形漸層

基本範例 :



<svg style="background-color:#afa;width:250px;height:50px">
<defs>
    <linearGradient id="a11122_linear1" gradientTransform="rotate(45)">
      <stop offset="0%" stop-color="#f00"></stop>
      <stop offset="100%" stop-color="#ff0"></stop>
    </linearGradient>
    <radialGradient id="a11122_rad1">
      <stop offset="0%" stop-color="#f00"></stop>
      <stop offset="100%" stop-color="#ff0"></stop>
    </radialGradient>
    <rect id="a11122_rect1" width="100" height="50" x="0" y="0"></rect>
    <rect id="a11122_rect2" width="100" height="50" x="0" y="0" fill="#00f"></rect>
    <!--  當這裡定義了 fill ,下面的 use 去設定 fill 會失效  -->
</defs>
<use xlink:href="#a11122_rect1" fill="url('#a11122_linear1')"></use>
<use xlink:href="#a11122_rect2" fill="url('#a11122_rad1')" x="120"></use>
</svg>

<button id="a11122_b1" style="font-size:1em;">取消填色</button>
<script>
$(function()
{
  $("#a11122_b1").on("click",()=>
  {
    $("#a11122_rect2").attr("fill","");
  });
});
</script>


linearGradient 線性漸層

參數 :

x1,y1,x2,y2x1,y1 漸層到 x2,y2 , 可以用 5% 或 0.5 表示
gradientTransformCSStransform 一樣
spreadMethod         pad(預設) , reflect , repeat
href                 引用別處定義的 stop

radialGradient 圓形漸層

cx,cy,r              圓形漸層的圓心座標和半徑
gradientTransformCSStransform 一樣
spreadMethod         pad(預設) , reflect , repeat
                     (經測試,在 MACSafariiphone 沒作用)
fx,fy,frcx,cy,r的圈圈中,漸層的中心點和半徑
href                 引用別處定義的 stop

範例 :



<svg style="background-color:#afa;width:400px;height:300px">
<defs>
    <linearGradient id="a11123_linear1" x1="0" y1="0" x2="0.5" y2="0.5" spreadMethod="pad">
      <stop offset="0%" stop-color="#f00"></stop>
      <stop offset="100%" stop-color="#ff0"></stop>
    </linearGradient>
    <linearGradient id="a11123_linear2" x1="0" y1="0" x2="0.5" y2="0.5" spreadMethod="repeat" href="#a11123_linear1"></linearGradient>
    <linearGradient id="a11123_linear3" x1="0" y1="0" x2="0.5" y2="0.5" spreadMethod="reflect" href="#a11123_linear1"></linearGradient>
    <radialGradient id="a11123_rad1" cx="0.5" cy="0.5" r="0.3" fx="0.5" fy="0.5" fr="0" spreadMethod="pad">
      <stop offset="0" stop-color="#f00"></stop>
      <stop offset="1" stop-color="#ff0"></stop>
    </radialGradient>
    <radialGradient id="a11123_rad2" cx="0.5" cy="0.5" r="0.3" fx="0.5" fy="0.5" fr="0" spreadMethod="repeat" href="#a11123_rad1"></radialGradient>
    <radialGradient id="a11123_rad3" cx="0.5" cy="0.5" r="0.3" fx="0.5" fy="0.5" fr="0" spreadMethod="reflect" href="#a11123_rad1"></radialGradient>
    <radialGradient id="a11123_rad4" cx="0.5" cy="0.5" r="0.3" fx="0.7" fy="0.7" fr="0" spreadMethod="pad" href="#a11123_rad1"></radialGradient>
    <radialGradient id="a11123_rad5" cx="0.5" cy="0.5" r="0.3" fx="0.7" fy="0.7" fr="0" spreadMethod="repeat" href="#a11123_rad1"></radialGradient>
    <radialGradient id="a11123_rad6" cx="0.5" cy="0.5" r="0.3" fx="0.7" fy="0.7" fr="0" spreadMethod="reflect" href="#a11123_rad1"></radialGradient>
</defs>
<rect width="90" height="50" x="0" y="0" fill="url('#a11123_linear1')" stroke="#000"></rect>
<rect width="90" height="50" x="100" y="0" fill="url('#a11123_linear2')" stroke="#000"></rect>
<rect width="90" height="50" x="200" y="0" fill="url('#a11123_linear3')" stroke="#000"></rect>
<rect width="90" height="90" x="0" y="60" fill="url('#a11123_rad1')" stroke="#000"></rect>
<rect width="90" height="90" x="100" y="60" fill="url('#a11123_rad2')" stroke="#000"></rect>
<rect width="90" height="90" x="200" y="60" fill="url('#a11123_rad3')" stroke="#000"></rect>
<rect width="90" height="90" x="0" y="160" fill="url('#a11123_rad4')" stroke="#000"></rect>
<rect width="90" height="90" x="100" y="160" fill="url('#a11123_rad5')" stroke="#000"></rect>
<rect width="90" height="90" x="200" y="160" fill="url('#a11123_rad6')" stroke="#000"></rect>
</svg>

既然可以用圓形漸層,就可以做出立體的球囉!


<svg style="width:400px;height:200px">
<defs>
  <radialGradient id="a11124_r1" cx="0.3" cy="0.3" r="0.7">
    <stop offset="0" stop-color="#eee"></stop>
    <stop offset="0.3" stop-color="#888"></stop>
    <stop offset="0.7" stop-color="#333"></stop>
    <stop offset="1" stop-color="#111"></stop>
  </radialGradient>
  <radialGradient id="a11124_r2" cx="0.7" cy="0.3" r="0.7" href="#a11124_r1"></radialGradient>
</defs>
<circle cx="100" cy="100" r="80" fill="url(#a11124_r1)"></circle>
<circle cx="300" cy="100" r="80" fill="url(#a11124_r2)"></circle>
</svg>