Translate

2020年5月30日 星期六

CSS 動畫

第一次看到 CSS 動畫,覺得為之驚豔,這樣做動畫比用 jquery 自己刻快多了,效果又好,然後就因為工作在忙的關係,又停止了學習的腳步。

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

這裡比較特別的是 cubic-bezier 可以利用這個網頁來計算 cubic-bezier, 四個參數其實是貝滋曲線的兩個控制點座標, 起點和終點為 0,0 和 1,1 , 套在 svg 上就是 <path d="M 0 0 C 參數1 參數2 參數3 參數4 1 1"/>


<style>
.a05301
{
  position:absolute;
  width:1em;
  height:1em;
  background-color:#f00;
  animation-name:a05301_a1; /* 名稱 */
  animation-duration:3000ms; /* 動畫時間 */
  animation-delay:0ms; /* 延遲 */
  animation-iteration-count:infinite;
  /*
      播放次數
      infinite  代表無限次播放
  */
  animation-timing-function:ease;
  /*
      預設 ease
      linear, ease-in, ease-out, ease-in-out
      step-start, step-end, steps(int,start/end), 
      cubic-bezier(n,n,n,n)
  */
  animation-direction:alternate;
  /*
      播放方向
      normal 預設
      reverse 反向
      alternate 來回
      alternate-reverse 反向來回
  */
  animation-fill-mode:none;
  /*
      播放後
      none 回到前面
      backwards 回到前面
      forwards 停在後面
      both 前面或後面
  */
  animation-play-state:paused;
  /*
      running 播放中 
      paused  暫停
  */
}
.a05301_d1
{
    animation-timing-function:ease;
}
.a05301_d2
{
    animation-timing-function:linear;
}
.a05301_d3
{
    animation-timing-function:ease-in;
}
.a05301_d4
{
    animation-timing-function:ease-out;
}
.a05301_d5
{
    animation-timing-function:ease-in-out;
}
.a05301_d6
{
    animation-timing-function:cubic-bezier(0.6, -0.6, 0.1, 1.6);
}
.a05301_d7
{
    animation-timing-function:steps(3,start);
}
@keyframes a05301_a1
{
  0% 
  {
    left:8em;
  }
  2% 
  {
    left:8em;
  }
  50%
  {
    left:50%;
  }
  100%
  {
    left:95%;
  }
}  
#a05301_id1 input:checked ~ .a05301
{
  animation-play-state:running;
}

</style>


<div id="a05301_id1" style="line-height:1em">
<label for="a05301_id2">播放</label> 
<input id="a05301_id2" type="checkbox" checked=""><br><br>

<div class="a05301 a05301_d1"></div> ease        <hr>
<div class="a05301 a05301_d2"></div> linear      <hr>
<div class="a05301 a05301_d3"></div> ease-in     <hr>
<div class="a05301 a05301_d4"></div> ease-out    <hr>
<div class="a05301 a05301_d5"></div> ease-in-out <hr>
<div class="a05301 a05301_d6"></div> cubic-bezier<hr>
<div class="a05301 a05301_d7"></div> steps       <hr>
</div>  

<br>  



ease
linear
ease-in
ease-out
ease-in-out
cubic-bezier
steps


Event

CSS 的屬性 animation-name 被修改後,動畫會重新開始,如果只給同一個名字是沒有用的,所以這裡的解決方法是先設 animation-name="" 然後設個 timeout 後再修改為要播放的動畫名稱

Ani2 的按鈕是利用新增 CSS 來取代本來的腳本,在 chrome 和 firefox 上,取代後會立即生效,但是在 safari 和 iphone/ipad 上卻會等動畫結束重新開始後才會套用新的腳本, Ani3 是取代後直接呼叫套用的 function 讓動畫重新開始。

animationstart 開始播放
animationend 播放結束
animationiteration 動畫開始重複
animationcancel 取消



<style>
.a05302
{
  position:absolute;
  width:1em;
  height:1em;
  background-color:#f00;
  animation-name:"";
  animation-duration:3000ms;
  animation-iteration-count:2;
  animation-timing-function:ease;
  animation-direction:normal;
  animation-fill-mode:both;
  animation-play-state:paused;
}
@keyframes a05302_a1
{
  0% 
  {
    left:10%;
  }
  50%
  {
    left:50%;
  }
  100%
  {
    left:90%;
  }
}  
@keyframes a05302_a2
{
  0% 
  {
    left:10%;
  }
  50%
  {
    transform:translate(0,3em);
    left:50%;
  }
  100%
  {
    left:90%;
  }
}
#a05302_play:checked ~ .a05302 
{
  animation-play-state:running;
}
#a05302_div button
{
  font-size:1em;height:1.7em;
}

</style>
<style id="a05302_newcss">
</style>

  <div id="a05302_div">
  播放 <input id="a05302_play" type="checkbox" checked=""><br>
  <div id="a05302_id1" class="a05302"></div>
  <br><br><br>
  <button id="a05302_change">Change</button>
  <button id="a05302_restart">Restart</button>
  <button id="a05302_ani2">Ani2</button>
  <button id="a05302_ani3">Ani3</button>

  <br>
  <br>
  <div id="a05302_id2" style="border:1px solid #000;padding:1em;"></div>
  </div>

<script>

(function()
{
  let o = $("#a05302_id1");
  let txt = $("#a05302_id2");
  let n=0;
  let a=["a05302_a1","a05302_a2"];
  let an=0;

  o.css("animation-name",a[an]); 

  function setani(o,name)
  {
    o.css("animation-name",""); 

    setTimeout(function()
    {
      o.css("animation-name",name); 
    },10);

  }

  o.on("animationstart",function()
  {
    if (++n>=2) {txt.html("");n=0;}
    txt.html(txt.html()+"開始.");
  });
  o.on("animationend",function()
  {
    txt.html(txt.html()+"結束.");
    setani(o,a[an]);

  });
  o.on("animationiteration",function()
  {
    txt.html(txt.html()+"重複.");
  });
  o.on("animationcancel",function()
  {
    txt.html(txt.html()+"取消.");
  });

  $("#a05302_change").on("click",function()
  { 
    an=(an+1)&1;
    setani(o,a[an]);
  });

  $("#a05302_restart").on("click",function()
  { 
    setani(o,a[an]);
  });

  $("#a05302_ani2").on("click",function()
  { 
    let cc = document.getElementById('a05302_newcss').sheet;
    try{cc.deleteRule(0);}catch(e){}
    cc.insertRule("@keyframes a05302_a2{"
      +"0%{left:10%;}50%{"
      +"transform:translate(0,3em);"
      +"left:50%;}100%{left:90%;}}", 0);    
  });
  $("#a05302_ani3").on("click",function()
  { 
    let cc = document.getElementById('a05302_newcss').sheet;
    try{cc.deleteRule(0);}catch(e){}
    cc.insertRule("@keyframes a05302_a2{"
      +"0%{left:10%;}50%{"
      +"transform:translate(0,-3em);"
      +"left:50%;}100%{left:90%;}}", 0);   

    setTimeout(function(){setani(o,a[an])},10);
  });


})();

</script>

播放






利用 CSS 動畫來實做一個指針時鐘,幾乎不需要寫程式,


<style>
.a05303
{
  position:absolute;
  width:8em;
  height:8em;
  animation-name:a05303_a1;
  animation-iteration-count:infinite;
  animation-timing-function:linear;
  animation-direction:normal;
  animation-play-state:running;
}
.a05303_h
{
  animation-duration:12000ms;
} 
.a05303_m
{
  animation-duration:1000ms;
} 
@keyframes a05303_a1
{
  0% 
  {
    transform:rotate(0deg);
  }
  100%
  {
    transform:rotate(360deg);
  }
}  
.a05303_d1
{
  position:absolute;
  left:calc( 50% - 2px );
  top:10%;
  width:4px;
  height:40%;
  background-color:#0000ff;
}
.a05303_d2
{
  position:absolute;
  left:calc( 50% - 2px );
  top:20%;
  width:4px;
  height:30%;
  background-color:#ff0000;
}
.a05303_circle
{
  border:1px solid #000;
  width:8em;
  height:8em;
  border-radius:4em;
}
</style>

<div class="a05303 a05303_m">
<div class="a05303_d1"></div>
</div>
<div class="a05303 a05303_h">
<div class="a05303_d2"></div>
</div>
<div class="a05303_circle"></div>

搭配 clip-path 可以設計多邊形變形的動畫


<style>
.a05304_bk
{
  position:relative;
  width:13em;
  height:13em;
  background:#fff;
}
.a05304_path
{
  position:absolute;
  width:13em;
  height:13em;
  background:#ffee88;
  animation-name:a05303;
  animation-iteration-count:1;
  animation-timing-function:ease;
  animation-direction:normal;
  animation-play-state:running;
  animation-duration:2000ms;
  animation-fill-mode:both;
}
.a05304_ball
{
  position:absolute;
  left:12em;
  top:6em;
  width:1em;
  height:1em;
  border-radius:0.5em;
  background:#f00;
  animation:a05304_a 3000ms 0s 1 linear running;
/*
  animation:a05304_a 3000ms 0s infinite linear running;
*/
}
@keyframes a05304_a
{
0%{left:12.00em;top:6.00em;}
33%{left:3.00em;top:11.20em;}
66%{left:3.00em;top:0.80em;}
100%{left:12.00em;top:6.00em;}
}
@keyframes a05304_a1
{
0%
{clip-path:polygon(96% 50% ,27% 90% ,27% 10% ,96% 50% ,96% 50% ,96% 50% ,96% 50% ,96% 50% )
}
100%
{clip-path:polygon(96% 50% ,50% 96% ,4% 50% ,50% 4% ,96% 50% ,96% 50% ,96% 50% ,96% 50% )}
}
#a05304 button
{
  font-size:1em;
}
</style>
<style id="a05304_css"></style>

<div id="a05304">

<div class="a05304_bk">
<div class="a05304_path"></div>
<div class="a05304_ball"></div>
</div><br>

<button class="a05304_b" nn="3">三角形</button>
<button class="a05304_b" nn="4">四邊形</button>
<button class="a05304_b" nn="5">五邊形</button>
<button class="a05304_b" nn="6">六邊形</button>
<button class="a05304_b" nn="7">七邊形</button>

</div>

<script>
$(function()
{
  function cn(n)
  { 
    let i;
    let j=100/n;
    let d=3.1415926*2/n;
    let hh="@keyframes a05304_b{";
    let pp="polygon(";
    let x,y;

    for (i=0;i<=n;i++)
    {
      x=(6+6*Math.cos(i*d));
      y=(6+6*Math.sin(i*d));
      hh+=(i*j)+"%{left:"+x+"em;top:"+y+"em;}";
      pp+=((x+0.5)*100/13).toFixed(0)+"% "
         +((y+0.5)*100/13).toFixed(0)+"% ,";
    }

    for (;i<=7;i++)
    {
      pp+=((x+0.5)*100/13).toFixed(0)+"% "
         +((y+0.5)*100/13).toFixed(0)+"% ,";
    }

    hh+="}";
    pp=pp.substr(0,pp.length-1)+")";

    return [hh,pp];
  } 
  function setani(o,name)
  {
    o.css("animation-name",""); 

    setTimeout(function()
    {
      o.css("animation-name",name); 
    },10);
  }

  let bakn=3;
  let bakpath="";

  function setn(n)
  {
    let css=document.getElementById("a05304_css").sheet;
    try{css.deleteRule(0);}catch(e){}
    try{css.deleteRule(0);}catch(e){}

    let r=cn(n);
    let r1=cn(bakn);

    css.insertRule(r[0]);

    css.insertRule("@keyframes a05304_a1{"
      +"0%{"
      +"clip-path:"+r1[1]+";transform:rotate(0deg);"
      +"-webkit-clip-path:"+r1[1]
      +"}"
      +"100%{"
      +"clip-path:"+r[1]+";transform:rotate(360deg);"
      +"-webkit-clip-path:"+r[1]
      +"}}");

    setani($(".a05304_path"),"a05304_a1");

    bakn=n;
    bakpath=r[1];
  }
  $(".a05304_path").on("animationend",function()
  {
    $(".a05304_path").css("clip-path",bakpath);
    $(".a05304_path").css("-webkit-clip-path",bakpath);
  });
  $(".a05304_ball").on("animationend",function()
  {
    setani($(".a05304_ball"),"a05304_b");
  });
  $(".a05304_b").on("click",(e)=>
  {
    setn( parseInt($(e.target).attr("nn")) );
  });

  setn(3);


});
</script>



2020年5月29日 星期五

CSS ::before and ::after

css 的 ::before 和 ::after 是我比較少接觸的 css 語法, 可以在元素前後加上一個不存在網頁元素中的物件,範例如下:


<style>
.txt
{
  color:#0000ff;
}
.txt::before
{
  content:"[" attr(btxt) "]";
/*
  content 一定要給值,語法有
  attr(屬性)
  url(網址) 可以秀圖片
*/
  color:#ff0000;
}
.txt::after
{
  content:"[" attr(atxt) "]";
  color:#00ff00;
}
</style>

<span class="txt" btxt="Before" atxt="After">我是文字</span><br>

我是文字

content 雖然可以用 url 秀圖片,但是無法調整圖片大小,如果要設定圖片大小,可以利用 background-image 來實現


<style>
.txt1
{
  color:#0000ff;
  position:relative;
}
.txt1::before
{
  content:"  [" attr(btxt) "]" ;
  color:#ff0000;
  display:inline-block;
  background-image: url("https://yiharng.github.io/bomb.png");
  background-repeat: no-repeat;
  background-size: 1.5em;
  height:2em;
}
.txt1::after
{
  content:"[" attr(atxt) "]";
  color:#00ff00;
}
</style>

<span class="txt1" btxt="Before" atxt="After">我是文字</span>

我是文字

CSS 的選擇器語法除了用 .class 還可以用 [] 來指定特定的attr , 如果搭配 hover 可以做到滑鼠移過去顯示說明的效果, 這個效果以前都是用 javascript 製作,其實 CSS 就可以做到了 !



<style>
span[stxt]:hover
{
  position:relative;
  color:#ff00ff;
  cursor:help;
}
span[stxt]:hover::after,
span[stxt]:focus::after
{
  position:absolute;
  background-color:#ffffcc;
  color:#0000ff;
  padding:0.5em;
  border:1px solid #000000;
  width:6em;
  text-align:center;
  left:0;
  top:1.2em;
}
span[stxt=ok1]:hover::after,
span[stxt=ok1]:focus::after
{
  content:"我是說明1";
}
span[stxt=ok2]:hover::after,
span[stxt=ok2]:focus::after
{
  content:"我是說明2";
}
</style>

<span stxt="ok1">我是文字1</span>
<span stxt="ok2">我是文字2</span>

我是文字1 我是文字2


::before 和 ::after 有個特別的功能是 counter , 我個人覺得這個其實就是 <ul> <ol> <li> 的原型

特別的是,自己寫有更靈活的設定,例如可以設定數字間隔,或多個計數器

範例如下 :



<style>

.nn,.f0
{
  counter-reset:var1 var2 2;
/*
  counter-reset:變數1 起始值 變數2 起始值
  沒有設定起始值就是 0
*/
}
.f1::before
{
  counter-increment: var1;
  content: counter(var1) ". ";
}
.f2::before
{
  counter-increment: var2 2;
  content: counter(var2,cjk-ideographic) ". ";
  color:#ff0000;
/*
  counter 語法 : counter(變數,list-style-type)
*/
}
.nn div{
  margin-left:1em;
}
</style>

<div class="nn">
開始
<div class="f0">
  <div class="f1">一樓</div>
  <div class="f1">一樓
    <div class="f0">
      <div class="f1">二樓</div>
      <div class="f1">二樓
        <div class="f0">
          <div class="f1">三樓</div>
          <div class="f1">三樓
            <div class="f2">F2</div>
            <div class="f2">F2</div>
            <div class="f2">F2</div>
          </div>
          <div class="f1">三樓</div>
        </div>
      </div>
      <div class="f1">二樓</div>
    </div>
  </div>
  <div class="f1">一樓</div>
</div>
結束
</div>
開始
一樓
一樓
二樓
二樓
三樓
三樓
F2
F2
F2
三樓
二樓
一樓
結束

list-style-type 的值如下:

disc , circle , square , armenian , cjk-ideographic , decimal , decimal-leading-zero , georgian , hebrew , hiragana , hiragana-iroha , katakana , katakana-iroha , lower-alpha , lower-greek , lower-latin , lower-roman , upper-alpha , upper-greek , upper-latin , upper-roman , none , inherit

除了 counter ,還有一個 counters 可以處理階層問題



<style>

.snn .sf0
{
  counter-reset:var1;
}
.snn .sf1::before
{
  counter-increment: var1;
  content: counters(var1,"-") ". ";
/*
  counters 語法 : counters(變數,連結字串,list-style-type)
*/
}
.snn div{
  margin-left:1em;
}
</style>

<div class="snn">
開始
<div class="sf0">
  <div class="sf1">一樓</div>
  <div class="sf1">一樓
    <div class="sf0">
      <div class="sf1">二樓</div>
      <div class="sf1">二樓
        <div class="sf0">
          <div class="sf1">三樓</div>
          <div class="sf1">三樓</div>
          <div class="sf1">三樓</div>
        </div>
      </div>
      <div class="sf1">二樓</div>
    </div>
  </div>
  <div class="sf1">一樓</div>
</div>
結束
</div>
開始
一樓
一樓
二樓
二樓
三樓
三樓
三樓
二樓
一樓
結束

這個功能的實用範例,是直接搭配 h1 和 h2 來實做章節



<style>

.hnn
{
  counter-reset: var1;
}
.hnn h1
{
  counter-reset: var2;
}
.hnn h1::before
{
  counter-increment: var1;
  content: "第" counter(var1) "章 " ;
}
.hnn h2::before
{
  counter-increment: var2;
  content: "第" counter(var1) "-" counter(var2) "節 ";
}
.hnn h2
{
  margin-left:1em;
}
.hnn span
{
  margin-left:3em;
}
</style>

<div class="hnn">
<h1>開始</h1>
  <h2>出發</h2>
    <span>一個旅程的開始
    </span>
  <h2>繼續</h2>
<h1>回程</h1>
  <h2>加油</h2>
  <h2>到家</h2>
</div>

開始

出發

一個旅程的開始

繼續

回程

加油

到家


取值和修改

javascript 有一個超強的指令,可以取得該元素所有的設定

window.getComputedStyle('元素', '::before 或 ::after')



<style>

.r6css1::before
{
  content: "[" attr(btxt) "]";
  color:#0000ff;
}
</style>

<div class="r6css1" btxt="我是Before">
  我是文字
</div>
<br>
<div class="r6css2">
</div>

<script>

(function()
{
  let dd=document.querySelector(".r6css1");
  let dd1=window.getComputedStyle(dd,"::before");
  $(".r6css2").html(
     "color:"+dd1.color+"<br>"
    +"content:"+dd1.content
  );
})() 

</script>
我是文字

color:rgb(0, 0, 255)
content:"[我是Before]"

::before 和 ::after 並不允許直接修改內容,最簡單的方法,是用新的 CSS 取代舊的

範例 :



<style>
.r7css1::before
{
  content: "[" attr(btxt) "]";
  color:#0000ff;
}
</style>
<style id="r7css_new">
</style>

<div class="r7css1" btxt="我是Before">
  我是文字
</div>
<br>
  <button id="r7b1">紅色</button>
  <button id="r7b2">綠色</button>

<script>

(function()
{
  let cc = document.getElementById('r7css_new').sheet;
  cc.insertRule(".r7css1::before{color:#0000ff;}", 0);  

  $("#r7b1").on("click",()=>
  {
    let cc = document.getElementById('r7css_new').sheet;
    cc.deleteRule(0);
    // 語法 deleteRule(索引)  
    cc.insertRule(".r7css1::before{color:#ff0000;}", 0);  
    // 語法 insertRule("css", 索引); 索引可省略
  });
  $("#r7b2").on("click",()=>
  {
    let cc = document.getElementById('r7css_new').sheet;
    cc.deleteRule(0);
    cc.insertRule(".r7css1::before{color:#00ff00;}", 0);  
  });
})() 

</script>
我是文字



2020年5月28日 星期四

這個 BLOG 的文章使用的技巧

  

雖然使用的是 Google 的 Blogger , 但是所有文章都是用 Markdown 寫的,我在 GitHub Pages 上寫了個小程式, 可以即時轉換,用來做小型開發測試還不錯用。

markdown 的處理使用的是 marked.js 以及 highlight.js 來處理程式碼的顏色,另外還寫了一支 mm.js,只要在 textarea 加上 class="markdown_mm" 再引用 mm.js 就會自動轉換,另外還可以加上 zoom="true" ,就會出現改變字型大小的按鈕

為了讓程式看起來更簡潔,mm.js 裡頭把 jquery 包了進來,文章中的所有範例都是預設使用 jquery,如果要執行文章中的範例程式,請在程式中引用 jquery 或 mm.js , 另外所有範例都會去掉HTML檔頭包括 <html> <head> <body> 這些宣告,只要將範例程式放在 body 中即可。

以這篇文章為例,在 Blogger 看 HTML 原始碼長這樣

<textarea class="markdown_mm" zoom="true">

  文章放這裡

</textarea>
<script src="https://yiharng.github.io/mm.js"></script>

其中比較需要處理的,大概是文章中不能使用 textarea ,如果需要的話,可以用 $textarea> ,和 $/textarea> 取代

mm.js 的其它應用

在 div 中加上 class="mmresult" 會出現 "執行結果" 的框框, 還可以加上另一個 class 的 [名稱],然後在程式碼區加上 $mmcode([名稱]) 用來取程式碼,這樣子文件就不需要程式碼一份,執行結果又一份

範例 :

```

$mmcode(r1)

```

<div class="mmresult r1">
  <font color="#0000ff">結果秀在這</font>
</div>

$mmcode(r1) 會被 <font color="#0000ff">結果秀在這</font> 取代


  <font color="#0000ff">結果秀在這</font>
結果秀在這

About me

# 關於我 早期約 2001 年, 獨立開發網頁[電子地圖EGM](http://yiharng.moralist.com.tw/EGM/),開始上班後設計另一個更完整的電子地圖圖台,然後用這個地圖核心改寫為導航程式,在 **Motorola 明** 手機上執行,還曾經移殖到 symbian 的手機上,後來再修改成為早期導航王的地圖核心程式(android/ios)。接下來才開始接觸 GEE ,處理 GIS 專案,總之,一直以來都在做些 GIS 相關的工作。而現在是個自由工作者,雖然收入不多,但是是做著自己喜歡的事。如果有人有合作意願,也歡迎來信。 為什麼要寫這個部落格呢 ? 自從發現現在的 javascript 等前端技術越來越強大,雖然想多深入瞭解一些細節,卻總是因為專案時程太趕,資料查一查然後解決問題,然後就忘了所使用的技巧,一直沒有時間讓我有系統的整理技術資料。離開職場成為自由工作者後, 思考著自己想要的是什麼?從網路上得到很多幫助,是不是也可以整理一下所學的技巧分享給需要的人呢?所以決定回到初學者的心情,好好的在這裡做個記錄,以及分享一些經驗及心得感想。[這個 BLOG 的文章使用的技巧](https://yhtpnotes.blogspot.com/2020/05/blog.html)
黃逸航 2020/5/28
yiharng@gmail.com



關於我

早期約 2001 年, 獨立開發網頁電子地圖EGM,開始上班後設計另一個更完整的電子地圖圖台,然後用這個地圖核心改寫為導航程式,在 Motorola 明 手機上執行,還曾經移殖到 symbian 的手機上,後來再修改成為早期導航王的地圖核心程式(android/ios)。接下來才開始接觸 GEE ,處理 GIS 專案,總之,一直以來都在做些 GIS 相關的工作。而現在是個自由工作者,雖然收入不多,但是是做著自己喜歡的事。如果有人有合作意願,也歡迎來信。

為什麼要寫這個部落格呢 ? 自從發現現在的 javascript 等前端技術越來越強大,雖然想多深入瞭解一些細節,卻總是因為專案時程太趕,資料查一查然後解決問題,然後就忘了所使用的技巧,一直沒有時間讓我有系統的整理技術資料。離開職場成為自由工作者後, 思考著自己想要的是什麼?從網路上得到很多幫助,是不是也可以整理一下所學的技巧分享給需要的人呢?所以決定回到初學者的心情,好好的在這裡做個記錄,以及分享一些經驗及心得感想。這個 BLOG 的文章使用的技巧


黃逸航 2020/5/28
yiharng@gmail.com