元素位置與座標

clientHeight / clientWidth

※inline元素會回傳0
回傳的寬 / 高,不包含border, margin, scrollbar的width

offsetHeight / offsetWidth

※inline元素會回傳0
回傳的寬 / 高,不包含margin,會包含border, scrollbar的width

scrollHeight / scrollWidth

有scrollbar時:回傳被隱藏的內容的高 / 寬,不包含border, margin
無scrollbar時:scrollHeight == clientHeight

clientTop

元素內層與外層之間的距離 (就是border的寬度)

offsetTop

元素到網頁最上方的距離(越過瀏覽器視窗,到body最上面)

scrollTop

回傳元素捲了多少px
元素捲到最底部時:scrollTop + clientHeight = scrollHeight

getBoundingClientRect()

回傳値包含小數點,client三兄弟只能回傳整數
回傳目標元素到可視範圍之間的距離
※iOS Safari等手機瀏覽器,網址bar會時而隱藏時而浮出,會導致數值變來變去

用法:#element.getBoundingClientRect().top

※注意
在mobile裝置上使用getBoundingClientRect()時
需與scrollX / scrollY併用
因為,手機瀏覽器的網址列,有時會浮出,有時會消失。
getBoundingClientRect()不會計算到這個情況

參考文章:http://uhyo.hatenablog.com/entry/2017/03/15/130825

function getAbsolutePosition(elm){
  const {left, top} = elm.getBoundingClientRect();
  return {
    left: left + window.scrollX,
    top: top + window.scrollY,
  };
}

【JavaScript】getter / setter

getter跟setter都是寫在物件底下的function方法

getter

給物件綁定訪問時(Access)可取得的函數

var obj = {
 log: ['example','test'],
 get latest() {
 if (this.log.length == 0) {
   return undefined;
  }else{
   return this.log[this.log.length - 1];
  }
 }
}

console.log(obj.log); //['example', 'test']
console.log(obj.latest); // "test"

setter

setter可以設定參數,然後讓物件回傳不同的值

//language 是一個物件
var language = {
  set current(name) {
    this.log.push(name);
  },
  log: []
}

language.current = 'EN'; //透過setter設定參數
console.log(language.log); // ['EN']

language.current = 'FA'; //透過setter設定參數
console.log(language.log); // ['EN', 'FA']

【JavaScript】select元素相關操作

#mySelect.options

回傳選項清單

select#mySelect
 option apple
 option banana
 option grape
console.log(mySelect.options)
//回傳所有options的集合
//型態為HTMLOptionsCollection

#mySelect.selectedIndex

回傳目前被選取到的index

select#mySelect
 option apple
 option banana
 option grape
console.log(mySelect.selectedIndex)
//0

【JavaScript】假的scroll-bar

步驟

  1. 隱藏原生的scroll-bar
  2. 用CSS做出一個假的bar
  3. 算出scroll-bar的長度
  4. 當發生滑動時,改變bar的top

隱藏原生的scroll-bar

  • 外層定義一個box,裡面要有假的bar與內容wrapper
  • 外層box設overflow: hidden
  • 內層wrapper設padding-left,這樣真正的scroll-bar就會超出外層box而被卡掉
.box#box
 .scroll-bar#bar
 .wrapper#wrapper
 img.items#items(src="https://ianchen.thisistap.com/wp-content/uploads/OOD.jpg" width="300px")
.box
 border: 1px solid #000
 width: 300px
 height: 300px
 overflow: hidden
  
.wrapper
 width: 100%
 height: 100%
 overflow-y: scroll
 padding-right: 28px

Display

用CSS做出一個假的bar

利用絕對定位將假bar定位在box的右上方

.box#box
 .scroll-bar#bar
 .wrapper#wrapper
.box
 border: 1px solid #000
 width: 300px
 height: 300px
 overflow: hidden
 position: relative
  
.wrapper
 width: 100%
 height: 100%
 overflow-y: scroll
 padding-right: 28px

.scroll-bar
 width: 8px
 height: 84px
 border-radius: 30px
 background-color: #7F7F7F
 position: absolute
 top: 0px
 right: 2.5px

Display

.

算出scroll-bar的長度

scroll-bar的兩種可能性

  1. 內容items的高度 小於 外層box的高度→不會出現scroll-bar
  2. 內容items的高度 大於 外層box的高度→出現scroll-bar
    scroll-bar的長度公式:外層box高度×(外層box高度÷內容items高度)
window.addEventListener("load", function(){
 
 if(items.clientHeight>=box.clientHeight){
  bar.style.display="none"; //如果內容items高度小於外層box,隱藏scroll-bar
 }else{
  bar.style.height=box.clientHeight * (box.clientHeight / items.clientHeight) + "px";
  //外層box高度×(外層box高度÷內容items高度)
 }
 
})

當發生滑動時,改變bar的top

  1. 當內容wrapper發生滑動→scroll事件
  2. 改變bar的top值→但是top值的MAX不是100
    計算top值的公式:目前items可視範圍離外層wrapper有多遠÷(items高度−wrapper高度)×(wrapper高度−bar高度)

 

 

 

customize scroll-bar

scrollbar height =
box.clientHeight * (box.clientHeight / content.offsetHeight)

【JavaScript】requestAnimationFrame

功能

楨率高的動畫函數。
setInterval最小時間單位是1毫秒,如果需要短時間大幅度的動態效果,則setInterval會顯得吃力。
這時就可以使用requestAnimationFrame

解説

requestAnimationFrame的callback函數的【第一個參數】會變成時間戳(=記錄當下的時間)

requestAnimationFrame(step);

function step(ggg){
 console.log(ggg); //會回傳當下的時間,但只會回傳一次
}

如果想要讓時間戳一直回傳過來,就要在callback函式裡面呼叫requestAnimationFrame

requestAnimationFrame(step);

function step(ggg){
 console.log(ggg);
 
 requestAnimationFrame(step); //會一直回傳時間戳過來
}

動畫的概念:

  1. 定義初始時間値,與變動時間値
  2. 紀錄毎次變動時間 減去 初始時間的相差値
  3. 相差値會越來越大
    requestAnimationFrame(step);
    
    //定義初始時間値
    let start=null;
    
    function step(ggg){
     if(start==null){
      start=ggg; //在一開始的時候帶入時間初始値
     }
     let passed=ggg-start; //算出現在時間與初始時間的相差値
     console.log(start); //會回傳初始時間値
     console.log(passed); //會回傳經過的時間
     requestAnimationFrame(step);
    }
  4. 用這個相差値帶入style的參數,就可以做出動態效果
    button(onclick="requestAnimationFrame(step)") click me
    #animatebox
    #animatebox
     width: 300px
     height: 200px
     background-color: teal
     opacity: 0
    let start=null;
    
    function step(ggg){
     if(start==null){
      start=ggg;
     }
     let passed=(ggg-start)/1000; //透明的値是0~1,用除數把數値縮小
     console.log(passed);
     
     animatebox.style.opacity=passed;
     
     if(passed<1){
      requestAnimationFrame(step); //如果透明値大於1停止動畫
     }
     
    }

範例

【JavaScript】event.target

説明

配合click / mouserover事件使用,指出目前的target元素

.outer(onclick="action()")
 .inner
 span click me
.outer
 background-color: #eee
 width: 200px
 height: 40px
 text-align: center
 .inner
 line-height: 40px
 display: inline-block
function action(){
 console.log(event.target);
}

應用例:收合選單

如果選單的標題與內容不小心寫在同一層級,
事件又想要綁在最外層的元素,
可以配合contains鎖定事件觸發的目標。

ul
 li(onclick="show(this)")
 .title 第一層選單 click me
 .content 這是第一層選單的內容
 br
 |內容...
 br
 |內容...
 br
 |內容...
 li(onclick="show(this)")
 .title 第二層選單 click me
 .content 這是第二層選單的內容
 br
 |內容...
 br
 |內容...
 br
 |內容...
ul
 display: inline-block
li
 list-style: none
 background-color: LightCyan 
 color: teal
 .title
 padding: 5px 10px
 .content
 background-color: Teal
 color: #fff
 padding: 5px 10px
 display: none
 .content.show
 display: block
function show(th){
 if(event.target.classList.contains("title")){
 th.children[1].classList.toggle("show");
 }
}

Display

  • 第一層選單 click me
    這是第一層選單的內容
    內容…
    內容…
    內容…
  • 第二層選單 click me
    這是第二層選單的內容
    內容…
    內容…
    內容…

【JavaScript】事件屬性的參數

説明

【事件屬性】
設定事件時,不使用事件綁定(addEventListener)
而將事件直接寫在tag的on事件屬性裡面

this的用法

  1. ATTR的括弧裡面要寫this(要寫完整的this,不能任意命名)
  2. function內要放第一個變數,變數名稱自定義,不能用this
.element(onclick="getAction(this)") click me
function getAction(th){
 console.log(th);
}

//"<div class='element' onclick='getAction(this)'>click me</div>"

※注意

如果在function裡面直接用this,會變成叫出window (global)

.element(onclick="getAction(this)") click me
function getAction(th){
 console.log(this);
}

//Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}

event的用法

  1. function內直接用event就好
.element(onclick="getAction()") click me
function getAction(m){
 console.log(event);
}

//MouseEvent {isTrusted: true, screenX: 47, screenY: 162, clientX: 47, clientY: 65, …}

【JavaScript】customize select-picker

説明

利用JS與CSS,自定義select-picker的style

  1. 用<select>跟<option>做出真選單
  2. 隱藏掉<select>(隱藏真選單)
    〔JS部分〕
  3. 讀取真選單<select>的內容,抓到後會是NodeList形式,給它轉換成陣列
  4. 換成陣列後,用mapping method,填充成<li>option內容</li>的形式
  5. 做出一個假的option wrapper / open-bar,然後把<li>們放進去

範例

引用函式庫:FontAwesome

.selector
 select
 option 台灣
 option 香港
 option 柬埔寨
 option 越南
 option 緬甸
 option 澳洲
 option 美國
 option 中國
 option 澳洲
 option 法國
 option 印尼
 option 英國
br
br
.selector
 select
 option good
 option apple
 option orange
 option red
 option beauty
 option view
 option nonono
 option yes
 option nice
.selector
 select
  display: none
 
.select-picker
 .open-bar
  width: 190px
  height: 30px
  line-height: 30px
  padding-left: 10px
  border-radius: 3px
  border: 1px solid gray
  user-select: none
  &:after
   content: "\f0d7"
   font-family: "FontAwesome"
   float: right
   margin-right: 10px
 
 .items
  max-width: 650px
  display: none
  border: 1px solid #000
  padding: 15px
  margin: 0
  position: absolute
  z-index: 999
  background-color: #fff  

 .item
  width: 120px
  display: inline-block
  height: 25px
  list-style: none
  padding: 5px 15px
 
 .items.items-show
  display: block
  display: inline-block
  
 .item.item-active
  background-color: #eee 
const selector=document.querySelectorAll(".selector");

selector.forEach(wrapper => {
 
 const RawData=wrapper.querySelector("select");
 const options=Array.from(RawData.children).map((item, n) => `<li class="item" onclick="select(${n}, this)">${item.textContent}</li>`).join("");
 
 //Create Content
 wrapper.innerHTML+=
  `<div class="select-picker">
    <div class="open-bar" onclick="showBlock(this)">${RawData.options[RawData.selectedIndex].text}</div>
     <ul class="items">
     ${options}
     </ul>
   </div>
  </div>`;

 
});


function showBlock(e){
 e.parentNode.children[1].classList.toggle("items-show");
}


function select(index, ele){
 
 const RawData=ele.closest(".selector").children[0];
 const itemActive=ele.closest(".selector").querySelectorAll(".item-active");
 
 
 if(itemActive.length>0) {
  itemActive[0].classList.remove("item-active");
 }
 RawData.selectedIndex=index;
 ele.parentNode.parentNode.children[0].textContent=RawData.options[RawData.selectedIndex].innerHTML;
 ele.parentNode.classList.remove("items-show");
 ele.classList.add("item-active");
}

Display

【JavaScript】JS的位置

説明

在HTML檔案中,JS不論是寫在頁面上,或是用檔案引入,都建議放在body後面。

理由:HTML文件的讀取方式是由上而下,如果寫在body前面的話,網頁還沒讀完JS就執行了,所以DOM會抓不到東西。

但若一定要寫在body前面的話,可以在JS內容外面加上一層load事件,讓網頁全部讀完後,再執行JS。

window.addEventListener("load", function(){

 //這邊放入JS內容。

});

【JavaScript】smalot-bootstrap-datetimepicker

説明

這是一套基於BS的函式庫,可以快速製作時間選擇器的小元件。

【必裝的函式庫】

屬性介紹

format【字串形式】

意義
p am / pm
P AM / PM
s 秒(沒有0開頭)
ss 秒(有0開頭)
i 分(沒有0開頭)
ii 分(有0開頭)
h 時(沒有0開頭,24小時制)
hh 時(有0開頭,24小時制)
H 時(沒有0開頭,12小時制)
HH 時(沒有0開頭,12小時制)
d 日期(沒有0開頭)
dd 日期(有0開頭)
m 數字月份(沒有0開頭)
mm 數字月份(有0開頭)
M 英文月份簡寫
MM 英文月份全名
yy 西元年(末二位數)
yyyy 西元年(四位數)

StartView / MinView 【數字形式】

在「只選擇月份」或是「只選擇日期」的情況下,預設的介面卻可以指定到某天的分鐘,會不太適合。
可以透過調整這2個參數達成更精準的控制

  • StartView:一開始選擇時間的介面〔預設:2〕
  • MinView:最後選擇時間的介面〔預設:0〕
意義
0 【選分鐘】hour view
1 【選小時】day view
2 【選日期】month view
3 【選月份】year view
4 【選年份】10-year view

範例:選擇日期

.input-append.date.form_datetime(data-view='hour')
 input(size='16', type='text', value='', readonly='', placeholder='2017-11-15 16:35')
 span.add-on
 i.fa.fa-calendar
$(".form_datetime[data-view='hour']").datetimepicker({
 format: "yyyy-mm-dd", //這邊可以自訂時間表示的格式
 autoclose: true,
 minView: 3
 });

範例:選擇時與分

.input-append.date.form_datetime(data-view='hour')
 input(size='16', type='text', value='', readonly='', placeholder='2017-11-15 16:35')
 span.add-on
 i.fa.fa-calendar
$(".form_datetime[data-view='hour']").datetimepicker({
 format: "yyyy-mm-dd hh:ii",
 autoclose: true
 });

範例:選擇月份

.input-append.date.form_datetime(data-view='year')
 input(size='16', type='text', value='', readonly='', placeholder='2017-11')
 span.add-on
 i.fa.fa-calendar
$(".form_datetime[data-view='year']").datetimepicker({
 format: "yyyy-mm",
 autoclose: true,
 startView: 4,
 minView: 3
 });

Display

CODEPEN

 

 

【JavaScript】load HTML 外部載入HTML

用途

在一個HTML檔案的部分區域引入其他外部HTML

▼示意圖

上圖使用了2個外部HTML,檔案結構如下

  • 主頁面〔index.html〕
  • 主選單(橫條選單)〔main-nav.html〕
  • 側選單(直條選單)〔verti-nav.html〕

在主頁面〔index.html〕頁面中,引入2個選單HTML〔main-nav.html〕&〔verti-nav.html〕

native JS 方法

const xhr=new XMLHttpRequest();
xhr.open("GET", "../verti-navs.html", true);
xhr.send();

xhr.onreadystatechange=function(){
 if(xhr.readyState==4 && xhr.status==200){

    document.querySelectorAll(".verti-nav")[0].innerHTML = xhr.responseText;

    }
};

jQuery 方法:load

$(".verti-nav").load("verti-navs.html");

觀看環境

外部載入HTML、ajax等做法會涉及到檔案之間的溝通,所以無法普通地在本機電腦打開觀看。需要透過以下的環境打開檔案。

觀看環境一:localhost

將電腦設定成虛擬主機(localhost),有以下幾種方法
  • 安裝XAMPP
  • 使用Webpack+Node.js
  • 利用終端機內建的python設定localhost環境

以下介紹用終端機設定localhost環境的方法

cd /Users/Desktop/web 
#指定要打開的資料夾

python -m SimpleHTTPServer 9099

輸入完畢後開啟瀏覽器,打開網址:http://localhost:9099
就會跳出index.html的頁面

觀看環境二:FireFox

FireFox也支援檔案之間的溝通。
但是,必須要先從index.html打開,再用index.html連到其他的頁面。否則FF會抓不到根目錄。

JS的撰寫方式

針對load進來的選單HTML撰寫JS時,有時會發生選單相關JS沒有被執行到的狀況。(特別在localhost環境下觀看時)

原因:JS的語言特性會偷跑,如果所有HTML的JS都要統一管理成同一份檔案,必須考慮到瀏覽器載入HTML的順序,再安排JS的撰寫位置。

一般來說,瀏覽器載入檔案的順序會是這樣:

  1. 主HTML檔案
  2. JS檔案
  3. load進來的選單HTML
    (因為選單load進來的動作是由JS控制,所以JS一定會比選單HTML早一步執行)

※因為JS檔案(內含選單相關JS)在選單HTML存在前就被載入了,所以當JS執行的時候並不存在選單HTML,導致JS的程式無效。

解決:

  1. 將選單相關JS寫在XMLHttpRequest的動作裡面。
  2. 並且增加一條判斷式:如果response存在,才執行選單JS
const xhr=new XMLHttpRequest();
xhr.open("GET", "../verti-navs.html", true);
xhr.send();

xhr.onreadystatechange=function(){
 if(xhr.readyState==4 && xhr.status==200){

    document.querySelectorAll(".verti-nav")[0].innerHTML = xhr.responseText;
    if(xhr.responseText){
       //這邊撰寫選單相關JS

     }
  }
};

【JavaScript】change event

説明

<input>狀態改變時→觸發事件
【例】下拉式選單:變更選項→觸發事件
【例】核取方塊:勾選 / 取消勾選→觸發事件

label Choose a color
 select#sel(name="colors" onchange="selShow()")
 option(value="Nothing") Select One...
 option(value="red") RED
 option(value="blue") BLUE
 option(value="green") GREEN 
#changeStatus
function selShow(){
 changeStatus.textContent=`You selected ${sel.value}`
}

Display

【JavaScript】transitionend event

説明

transitionend event:漸變結束後→觸發事件

#box(ontransitionend="transShow()") hover me
p#transStatus
#box
 border: 1px solid #000
 display: inline-block
 transition-duration: 0.5s
 &:hover
  font-size: 30px
function transShow(){
 transStatus.textContent="Transition is Done"
}

Display

hover me

【JavaScript】keydown and keyup event

説明

  • keydown event:鍵盤按下時→觸發動作
  • keyup event:鍵盤按下後鬆開→觸發動作

 

p Press any key
p#pressStatus
window.addEventListener("keydown", function(e){
 pressStatus.textContent=`Key code of ${e.keyCode} is pressed`;
})

window.addEventListener("keyup", function(){
 pressStatus.textContent=`Now you left the key`
})

Display

Press any key

【JavaScript】focus and blur event

説明

  1. focus event:聚焦(點選到)該元素→觸發事件
    ※可以用滑鼠點選觸發focus,某些情況下也可以用鍵盤選取(Tab鍵),觸發focus
  2. blur event:focus的相反,離開該元素後→觸發事件
input#getText(type="text")
button(onclick="getFocus()") Get focus
button(onclick="loseFocus()") Lose focus
function getFocus(){
 getText.focus();
}

function loseFocus(){
 getText.blur();
}

Display

【JavaScript】mouse event series

説明

  • mousedown:滑鼠按下去的瞬間→觸發事件
  • mouseout:滑鼠離開→觸發事件
  • mouseup:滑鼠按下、離開的瞬間→觸發事件

 

p Try to draw it ↓↓
canvas#canvas(width="300", height="300")
#canvas
 width: 300px
 height: 300px
 border: 1px solid #000
let isDrawing=false;
let lastX=0;
let lastY=0;
const ctx=canvas.getContext("2d");

function draw(e){
 if(!isDrawing){
  return;
 }

 ctx.beginPath();
 ctx.moveTo(lastX, lastY);
 ctx.lineTo(e.offsetX, e.offsetY);
 ctx.strokeStyle="#000000";
 
 [lastX, lastY]=[e.offsetX, e.offsetY];
 
 ctx.stroke();
}

//滑鼠按下→開始畫圖,計算起始座標
canvas.addEventListener("mousedown", (e) => {
 isDrawing = true;
 [lastX, lastY]=[e.offsetX, e.offsetY];
});

//滑鼠移動→繼續畫圖
canvas.addEventListener("mousemove", draw);
//滑鼠放掉→停止畫圖
canvas.addEventListener("mouseup", () => isDrawing = false);
//滑鼠離開→停止畫圖
canvas.addEventListener("mouseout", () => isDrawing = false);

Display
Try to draw it ↓↓

【JavaScript】mousemove event

説明

滑鼠移動→觸發事件

#screenBox Hover me
#locate
#screenBox
 width: 300px
 height: 200px
 background-color: #eee
 text-align: center
 line-height: 200px
 color: rgba(0,0,0,0.5)
screenBox.addEventListener("mousemove", function(e){
 locate.textContent=`Mouse locate is (${e.offsetX}, ${e.offsetY})`
})

Display

Hover me

【JavaScript】Parent, Children, Silbing

選取母元素

#parentBox
 .child A Box
 .child B Box
#parentBox
 width: 500px
 height: 500px
 background-color: #eee
 text-align: center
 line-height: 500px

 
.child
 width: 100px
 height: 100px
 display: inline-block
 line-height: 100px
 background-color: #beddcd
const childs=document.querySelectorAll(".child");
childs[0].parentNode; //會選到parentBox

選取子元素

※HTML, CSS 沿用上例

children Method 是 NodeList 形式(即便只有一個子元素),所以要向使用 querySelectorAll 的時候一樣,指定 index

parentBox.children[0]; //會選到A Box

選取相鄰元素

※HTML, CSS 沿用上例

Native JavaScript 沒有辦法直接指定相鄰元素(jQuery 的話可以使用sibling()),所以只能先回到母元素→再指定子元素

const childs=document.querySelectorAll(".child");

childs[0].parentNode.children[1]; //會選到B Box

【JavaScript】滑鼠相關屬性

滑鼠相關屬性

clientX & clientY

定位基準:視窗長&寬
視窗長&寬→window.innerHeight / Width
※視窗的範圍:瀏覽器的畫面,扣掉上方網址列的部分。
如果把瀏覽器畫面,視窗也會跟著變小

▼示意圖▼

intro.addEventListener("mousemove", function(e){
   console.log(e.clientX, e.clientY);
 }
)

offsetX & offset Y

定位基準:目標元素的長&寬
目標元素的長&寬:id.clientHeight / Width

▼示意圖▼

intro.addEventListener("mousemove", function(e){
   console.log(e.offsetX, e.offsetY);
 }
)

pageX & pageY

定位基準:整個頁面的長&寬
整個頁面的長&寬:document.body.clientHeight / Width
※【沒設定頁面寬的情況】瀏覽器的寬度拉小,整個網頁頁面的寬度也會跟著變小(被壓縮了)
※【有設定頁面寬的情況】瀏覽器的寬度拉小,整個網頁頁面的寬度不會跟著變小(會出現橫向的卷軸)

▼示意圖▼

intro.addEventListener("mousemove", function(e){
   console.log(e.pageX, e.pageY);
 }
)

screenX & screenY

定位基準:電腦畫面的長&寬
電腦的長&寬:screen.height / width
※電腦整個畫面:上方有包含網址列,下方有包含工具列
※跟clientX / Y 的差別:定義的外圍是電腦畫面,所以即便拉小瀏覽器視窗,也不會有任何影響

▼示意圖▼

intro.addEventListener("mousemove", function(e){
   console.log(e.screenX, e.screenY);
 }
)

【JavaScript】load事件 網頁打開後馬上觸發

説明

網頁一載入→觸發事件
【例】表單填入網頁:頁面一打開,焦點就對準輸入格。

範例

寫法一

監聽load事件

input#input(type="text" placeholder="input something...")
window.addEventListener("load", function(){
 input.focus();
})

寫法二

使用window.load

window.onload=input.focus();

Display

【JavaScript】Event Listener 監聽事件一覽

用法

①透過特定event觸發動作

用法基礎篇

目標物.addEventListener("特定的event", 
 function(){
  //這邊寫要觸發的動作
 }
)

②操控目標元素完成某動作

用#button按鈕操控,讓超連結#theURL被點擊

a#theURL(href="ianchen.thisistap.com") 超連結
br
button(onclick="openURL()") click it and open the URL
//onclick的用法參見下方
function openURL(){
 theURL.click();
}

Display

超連結

③ON+EVENT名稱:用HTML直接呼叫 function

基礎範例

HTML用法:on+event名=”函式名()”
※如果要呼叫event的話,需要使用這種形式:on+event名=”函式名(event)”

【例如】click event & makeItBigger function
→onclick="makeItBigger()" 
//這個東西放在HTML tag裡面
//記得要另外寫makeItBigger這個函式

【上述用法等同】
id.eventListener("click", makeitBigger());

滑鼠相關監聽事件

  1. click event:滑鼠按下→觸發事件
  2. mousemove event:滑鼠移動→觸發事件
  3. mousedown event:滑鼠按下去的瞬間→觸發事件
  4. mouseout event:滑鼠離開→觸發事件
  5. mouseup event:滑鼠按下、離開的瞬間→觸發事件

打字相關監聽事件

  1. focus event:聚焦(點選到)該元素→觸發事件
    ※可以用滑鼠點選觸發focus,某些情況下也可以用鍵盤選取(Tab鍵),觸發focus
  2. blur event:focus的相反,離開該元素後→觸發事件
  3. keydown event:鍵盤按下時→觸發動作
  4. keyup event:鍵盤按下後鬆開→觸發動作
  5. input event:輸入文字→觸發動作

其他監聽事件

  1. transitionend event:漸變結束後→觸發事件
  2. change event:<input>狀態改變時→觸發事件
    【例】下拉式選單:變更選項→觸發事件
    【例】核取方塊:勾選 / 取消勾選→觸發事件

 

【JavaScript】input相關Method

value

偵測文字輸入框的內容

input#input(type="text")
br
#show
window.addEventListener("keyup", function(){
 show.textContent=`What you input is "${input.value}"`;
})

Display

What you input is

checked


監測核取方塊被點選的狀態。並且傳回布林值

input#checkbox(type="checkbox")
br
p#ckeckStatus
btn.addEventListener("click", function(){
 ckeckStatus.innerHTML=`ckecked status: ${checkbox.checked}`;
})

Display


Facebook Pixel

官方文件

  1. 基本用法【PageView】
  2. 轉換率追蹤【Purchasing Event】
  3. 事件與參數用法【Event & Parameter】

範例

基本用法:導入PageView

<script> 
!function(f,b,e,v,n,t,s){if(f.fbq)return;n=f.fbq=function(){n.callMethod? 
n.callMethod.apply(n,arguments):n.queue.push(arguments)};if(!f._fbq)f._fbq=n; 
n.push=n;n.loaded=!0;n.version='2.0';n.queue=[];t=b.createElement(e);t.async=!0; 
t.src=v;s=b.getElementsByTagName(e)[0];s.parentNode.insertBefore(t,s)}(window, 
document,'script','//connect.facebook.net/en_US/fbevents.js'); 
fbq('init', '這邊放入FB頁面的ID'); 
fbq('track', 'PageView'); 
</script> 

<noscript> 
 <img height="1" width="1" style="display:none" 
src="https://www.facebook.com/tr?id=這邊放入FB頁面的ID&amp;ev=PageView&amp;noscript=1" /> 
</noscript>

進階用法:轉換率追蹤

<script> 
!function(f,b,e,v,n,t,s){if(f.fbq)return;n=f.fbq=function(){n.callMethod? 
n.callMethod.apply(n,arguments):n.queue.push(arguments)};if(!f._fbq)f._fbq=n; 
n.push=n;n.loaded=!0;n.version='2.0';n.queue=[];t=b.createElement(e);t.async=!0; 
t.src=v;s=b.getElementsByTagName(e)[0];s.parentNode.insertBefore(t,s)}(window, 
document,'script','//connect.facebook.net/en_US/fbevents.js'); 
fbq('init', '這邊放入FB頁面的ID'); 
fbq('track', 'PageView');  
fbq('track', 'Purchase', {currency: '這邊放幣值', value: 這邊放金額}); 
//物件參數還可以設其他的,但是上面2個是必須指定。
//詳細參考事件與參數用法 
</script> 


<noscript> 
<img height="1" width="1" style="display:none" src="https://www.facebook.com/tr?id=這邊放入FB頁面的ID&amp;ev=PageView&amp;noscript=1" />  //↓如果有2個事件的話(例如本例),就要指定2組img tag
<img height="1" width="1" border="0" alt="" style="display:none" 
src="https://www.facebook.com/tr?id=107593379985738&amp;ev=Purchase&amp;cd[value]=這邊放金額&amp;cd[currency]=這邊放幣值" />  
//【&amp;】是&的轉換字元,用來連結Purchase事件的物件資料。
//因為本例只有2組物件參數,所以用了一個&amp;連結彼此
//若使用更多物件參數,就需要追加&amp;連接
//用法請參考轉換率追蹤 </noscript>

除錯方法

  1. 準備好FB Pixel程式碼
  2. 下載Chrome外掛程式:Facebook Pixel Helper
  3. 用CODEPEN打開一個PEN,把程式碼貼在HTML的地方
  4. 用Facebook Pixel Helper檢查程式碼有沒有成功執行(成功會有綠色的勾勾)

 

【Vue.js】ready指令 API串接

説明

用ready串接後台API

範例

API載入純文字

#app
 p {{text}}
const vm=new Vue({
 el: "#app",
 data: {
  text: ""
  },
 ready: function(){
  $.ajax({
   url: "https://awiclass.monoame.com/api/command.php?type=get&name=notifydata",
   success: function(res){
    vm.text=res;
    }
   })
  }
 
})

Display

哈囉!! 這邊是你用AJAX載入的純文字公告!!

API載入JSON

要先轉換成JSON格式,否則會變成純字串

#app
 ul
  li(v-for="item in items") 【{{item.name}}】${{item.price}}
const vm=new Vue({
 el: "#app",
 data: {
  items: []
  },
 ready: function(){
  $.ajax({
   url: "https://awiclass.monoame.com/api/command.php?type=get&name=itemdata",
   success: function(res){
    vm.items=JSON.parse(res); //轉換成JSON格式
    }
   })
 }
 
})

Display

  • 【吹風機】$300
  • 【麥克筆】$9000
  • 【筆記型電腦】$54555
  • 【Iphone 9】$32000
  • 【神奇海螺】$5000
  • 【PSP1007】$2000
  • 【鍵盤】$3500

【JavaScript】Ajax

説明

jQuery版本
使用原生的JavaScript串接Ajax

  1. 建立HTTPRequest請求  new XMLHttpRequest()
  2. 建立回應函式  onreadystatechange
  3. 確認readyState、status(HTTP狀態碼)OK
  4. 執行回應動作,回應的值為responseText
  5. 定義後端api網址與method  open()
  6. 前端傳送資料  send()

readyState

 值  狀態  說明
 0  UNSET  尚未讀取
 1  OPENED  讀取中
 2  HEADERS_RECEIVED  已下載完畢
 3  LOADING  資訊交換中
 4  DONE  成功

status

status為200,表示成功

HTTP狀態碼一覽

範例

JavaScript版

p#p
const xmlhttp=new XMLHttpRequest();

xmlhttp.onreadystatechange=function(){
 if(this.readyState==4 && this.status==200){
  p.textContent=this.responseText; //API連成功的動作
 }
};

xmlhttp.open("GET", "https://awiclass.monoame.com/api/command.php?type=get&name=notifydata");//API網址
xmlhttp.send();

Display

哈囉!! 這邊是你用AJAX載入的純文字公告!!

jQuery版

p#p
$.ajax({
 url: "https://awiclass.monoame.com/api/command.php?type=get&name=notifydata",
 success: function(res){
  p.textContent=res;
 }
})

Display

哈囉!! 這邊是你用AJAX載入的純文字公告!!

用ajax取得header, menu等共用HTML

#header
var headerXhr=new XMLHttpRequest();

headerXhr.open('GET', 'header.html', true);
headerXhr.send();

headerXhr.onreadystatechange=function(){
    if(headerXhr.readyState==4 && headerXhr.status==200){
        header.innerHTML = headerXhr.responseText;
    }
};

用ajax傳送JSON

TIPS: 先把JSON轉成string格式,再傳送

var xmlhttp=new XMLHttpRequest();

xmlhttp.open('POST', 'http://9.102.60.125:5000/api/bbinqcan', true);
xmlhttp.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
xmlhttp.onreadystatechange=function(){

    if(xmlhttp.readyState==4 && xmlhttp.status==200){
        res.textContent=xmlhttp.responseText;
    }
};

xmlhttp.send(
        //checkedJSON是一個JSON格式的變數
    JSON.stringify(checkedJSON);
)

【Vue.js】v-on

説明

methods寫在JS裡面,v-on寫在HTML裡面。
功能類似原生JS的addEventListener。

範例

#app
 button(v-on:click="show") click me
 p#vueText some text
#vueText
 color: #fff
var vm=new Vue({
 el: "#app",
 methods: {
 show: function(){
  vueText.style.color="#000"
  }
 }
})

Display

some text

【JavaScript】子物件指定

説明

JS中有2種方式可以指定子物件。

方法一:dot連接

常用方法,利用dot連接母物件與子物件

const obj={
 akasha: 222,
 typhoon: 15151515,
 tks: "33333333"
}

console.log(obj.akasha); //222

方法二:中括號

母物件後面用中括號,中括號裡面可以用字串指定,或是用變數代換

const obj={
 akasha: 222,
 typhoon: 15151515,
 tks: "33333333"
}

//字串指定法
console.log(obj["akasha"]); //222

/*****************************/

//變數代換法
const typ = "typhoon";
console.log(obj[typ]); //15151515

 

【Vue.js】v-if

使用方法

①data物件裡面建立會用到布林値的變數
②用v-model操控使用者變更布林値
③v-if=”變數名稱”,當布林値為真,顯示某些元素

範例

當核取方塊被點選,就會顯示下方文字

#app
 label Show text
 input(type="checkbox" v-model="show")
 p the value of show is {{show}}
 hr
 p(v-if="show") Hey the box is clicked !!
var vm=new Vue({
 el: "#app",
 data: {
 show: false
 }
});

Display

the value of show is false


【Vue.js】v-model 雙向綁定

說明

v-model的功能:
使用者輸入資料→JS物件同步更新→及時渲染

用法:v-model=”變數名稱”

範例

同步更新文字

#app
 label 姓名:
 input(v-model="name")

/********************/

h4 您好,{{name}}
var vm=new Vue({
 el: "#app",
 data: {
  name: "陳怡安"
 }
});

同步更新style

#app
 label 顏色:
 input(v-model="color")
/********************/

p 下面這個方塊的顏色是【{{color}}】
.block(style="background-color: {{color}}")
.block
 border: 1px solid #000
 width: 40px
 height: 40px
var vm=new Vue({
 el: "#app",
 data: {
  color: "#C0C999"
 }
});

陣列用法

#app
 p 喜歡的書:
 //渲染3次
 div(v-for="(id, book) in books")
  label 【{{id+1}}】
 //印出書名
  input(v-model="book")
 
/******************/
p 喜歡的書
 ul 
 li(v-for="book in books") {{book}}
var vm=new Vue({
 el: "#app",
 data: {
  books: ["雨天的艾莉絲", "鋼鍊", "天地明察"]
 }
});

Display

CODEPEN

 

【Vue.js】v-for

基本用法

v-for=”項目 in 陣列”

#app
 ul
  li(v-for="item in shoplist") {{item}}
var vm=new Vue({
 el: "#app",
 data: shoplist: ["apple", "banana", "papaya"]
});

Display

  • apple
  • banana
  • papaya

物件形式與存取編號

編號用id表示

#app
 ul
  li(v-for="(id, item) in shoplist2") {{id+1}}. {{item.name}}
  br
  |${{item.price}}

//陣列的編號從0開始計算,但是一般商品的編號要從1開始,所以id+1
var vm=new Vue({
 el: "#app",
 data: shoplist2: {
 name: "apple",
 price: 1000,
 origin: ["美國", "日本"]
 },
 {
 name: "banana",
 price: 500,
 origin: ["台灣", "韓國", "菲律賓"]
 },
 {
 name: "papaya", 
 price: 300,
 origin: ["亞特蘭提斯", "印度"]
 }
});

Display

  • 1. apple
    $1000
  • 2. banana
    $500
  • 3. papaya
    $300

使用第二層v-for

#app
 ul
  li(v-for="(id, item) in shoplist2") 【編號】{{id+1}} 
  br
  |【品名】{{item.name}}
  br
  |【價錢】${{item.price}}
  br
  |【生產地】
  span(v-for="place in item.origin") </br>{{place}}
var vm=new Vue({
 el: "#app",
 data: shoplist2: {
 name: "apple",
 price: 1000,
 origin: ["美國", "日本"]
 },
 {
 name: "banana",
 price: 500,
 origin: ["台灣", "韓國", "菲律賓"]
 },
 {
 name: "papaya", 
 price: 300,
 origin: ["亞特蘭提斯", "印度"]
 }
});

Display

  • 【編號】1
    【品名】apple
    【價錢】$1000
    【生產地】
    美國
    日本
  • 【編號】2
    【品名】banana
    【價錢】$500
    【生產地】
    台灣
    韓國
    菲律賓
  • 【編號】3
    【品名】papaya
    【價錢】$300
    【生產地】
    亞特蘭提斯
    印度

 

 

【Vue.js】變數代換

説明

Vue.js是前端框架,用JS解決HTML資料處理。
→技術文件

HTML:寫模板
JS:代入資料
→解決①HTML重複撰寫太多②資料即時更新問題

應用

引入Vue.js

<head>
 <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.28/vue.js"></script>
</head>

建立模板與代入資料

#app
//指定Vue作用區域
 h1 Vue.js 使用大括號與變數代換
 hr
 p 我的名字叫做{{name}},來自{{city}}
 //用兩個大括號{{}}建立模板
 br
 |最近正在學習如何使用{{lang}},
 br
 |他其實就像是一個{{text}}
 p Vue.js的代換可以用物件來表示,甚至陣列來判斷。
//建立一個Vue, 即時監測資料更新狀況
var vm=new Vue({
 el: "#app", //指定作用區域
 data: { //用物件形式存放資料
 name: "陳怡安",
 city: "基隆市",
 lang: "Vue.js",
 text: "資料置換法",
 notation: "兩個大括號{{變數名稱}}",
 }
})

Display

我的名字叫做陳怡安,來自基隆市
最近正在學習如何使用Vue.js,
他其實就像是一個資料置換法

Vue.js的代換可以用物件來表示,甚至陣列來判斷。
只要在裡面用兩個大括號{{變數名稱}}包起來就可以了。

陣列與v-for列表渲染

#app
 p 我的技能有:
 /**************
 非v-for法
 ul 
  li {{skills[0]}}
  li {{skills[1]}}
  li {{skills[2]}}
  li {{skills[3]}}
 ****************/
 //v-for法
 ul
  li(v-for="skill in skills") {{skill}}
  //skill 可以自己命名
  //skills要根據vue撰寫的内容指定
var vm=new Vue({
 el: "#app",
 data: {
 skills: ["程式開發", "網頁設計", "翻譯", "口譯"],
});

Display

我的技能有:

  • 程式開發
  • 網頁設計
  • 翻譯
  • 口譯

 使用物件資料

#app
 p 我踏入網頁設計已經有{{infos.year}}年了,希望可以持續進步。
var vm=new Vue({
 el: "#app",
 data: {
 infos:{
 year: 1
  },
 }
});

Display
我踏入網頁設計已經有1年了,希望可以持續進步。

代入HTML程式碼

使用3組大括號代入HTML程式碼

#app
 p Vue.js除了預設帶入是一般的純文字以外,三組大括號可以代入HTML原始碼
 div {{{html}}}
.block
 width: 100px
 padding: 10px
 border: 1px solid #000
var vm=new Vue({
 el: "#app",
 data: {
  html: "<div class='block'>代入HTML</div>"
  //雙引號内的HTML字串裡面要改單引號
 }
});

Display

代入HTML

globalCompositeOperation 合成效果

説明

對canvas產生圖層混合的效果。
類似CSS的mix-blend-mode

範例

canvas#canvas(width="800", height="800")
html, body
 margin: 0
 padding: 0
 overflow: hidden
const ctx=canvas.getContext("2d");


ctx.fillStyle="#FFA500";
ctx.fillRect(400, 400, 100, 100);

//合成效果一覽
ctx.globalCompositeOperation="multiply";

ctx.fillStyle="#f7f4c8";
ctx.fillRect(450, 450, 100, 100);

Display
Display

destructuring assignment 分離指派式

説明

可以將陣列或物件的資料取出成獨立變數。
※通常用來大量指定變數,可以一行寫完。

範例

/********
以前的寫法
const a=1;
const b=2;
*********/

/***ES6新寫法***/
[a, b]=[1, 2]
//可以簡潔地指定一坨變數。

console.log(a, b)//1 2

canvas stroke 描繪線條

步驟

  1. 建立路徑
    beginPath()
  2. 設定線條寬度(可省)
    lineWidth=”5″
  3. 設定顏色(可省)
    strokeStyle=”green”
  4. 開始
    moveTo(0, 100)
  5. 結束
    lineTo(300, 100)
  6. 畫出線來
    stoke()

範例

canvas#canvas(width="300", height="150")
const ctx=canvas.getContext("2d");
canvas.width=window.innerWidth;
canvas.height=window.innerHeight;

//draw a line
ctx.beginPath();//start to draw a line
ctx.lineWidth="5";//set line width
ctx.strokeStyle="green";//set color
ctx.moveTo(0, 100);//start point
ctx.lineTo(300, 100);//end point
ctx.stroke();//draw it

Display

const

const 的特性

  1. 不可以被修改(再指定)
  2. 如果有block,在block裡面可以被改,但是回到外面會變回初始值

cant’t be updated & defined

const width=100;

width=200;
console.log(width);//100
//不可被修改

block scope

let points=50;
const winner=false;


if(points>40) {
 const winner=true;
 console.log(winner);//true
 //只有在block內部,才能夠被更改
}

console.log(winner);//false
//在外部則不會被更改,呈現初始值

By Value or By Reference

當資料類型為By Value時〔Number, String, Boolean〕
以const宣告的變數是不能被再指定的(即等於不能被改)

//錯誤示例
const name='Ian';
name='Chen'; //←ERROR!!

但若資料類型是By Reference時〔Object, Array〕,
以const宣告後可以用push, object.item=xxx等方式修改其值。
但一樣不能進行再指定的動作

//正確示例
const fruits=['apple', 'banana', 'orange'];
console.log(fruits); //['apple', 'banana', 'orange']

fruits.push('grape');
console.log(fruits); //['apple', 'banana', 'orange', 'grape']
//錯誤示例
const colors=['red', 'blue', 'green'];
colors=['pink', 'black', 'white'] //←ERROR!!

let

block

block是指大括弧 {} 裡面。
像是if述句,for述句,function裡面都是block

  • global scope→任何地方都能被改
  • block socpe→在if述句,for述句,function裡面可以被改(大括號包起來的地方),在外面還是回到初始值
  • function scope→只有在function裡面才可以被改

let 的特性

  1. 可以被修改
  2. 如果有block,在block裡面可以被改,但是回到外面會變回初始值

updated & defined

let width=100;

width=200;
console.log(width);//200
//可被修改

block scope

let points=50;
let winner=false;


if(points>40) {
 let winner=true;
 console.log(winner);//true
 //只有在block內部,才能夠被更改
}

console.log(winner);//false
//在外部則不會被更改,呈現初始值

var

var的特性

  1. 可以被重新定義(redefined)
  2. 可以被更新(updated)
  3. 若存在於函式內(function),就是函式變數(function scope),只能在函式內部被存取、修改。
  4. 若沒再函式內,會變成全域變數(global scope),不論在函式內或是外面都可以被存取,修改。

redefined and updated

var width=100;
console.log(width);//100

width=200;
console.log(width);//200
//可被修改

function scope

function setWidth() {
 var width = 100;
 console.log(width);//100
}

setWidth();
console.log(width);//nothing
//width只能在setWidth裡面作用

global scope

var width;
function setWidth() {
 width = 100;
 console.log(width);//100
}

setWidth();
console.log(width);//100
//width 在任何地方都能被修改或存取
let points = 50;
var winner = false;

if(points > 40) {
 var winner = true
 console.log(winner);//true
}

console.log(winner);//true
//因為if不是function,所以winner變成全域變數
//能在任何地方被修改與存取

splice() 陣列項目刪除

説明

刪除或增添陣列項目

範例

刪除項

陣列.splice(編號,刪除幾項)

【例】arr.splice(1, 1)
→從 arr[1] 開始刪除1項
→刪掉 arr[1]

const fruits = ['Banana', 'Orange', 'Lemon', 'Apple', 'Mango'];
fruits.splice(1, 1)//從fruits[1]開始,刪掉1項。

console.log(fruits);//["Banana", "Lemon", "Apple", "Mango"]

增添項

const fruits = ['Banana', 'Orange', 'Lemon', 'Apple', 'Mango'];
fruits.splice(1, 1, "Melon")//從fruits[1]開始,刪掉1項,插入“Moko”

console.log(fruits);//["Banana", "Melon", "Lemon", "Apple", "Mango"]

 

slice() 保留部分陣列項目

定義

指定陣列編號範圍,返回一組新的陣列

【必須値】開始,結束
※返回的値不會包含結束的編號,但會包含開始的那個編號

範例

const fruits = ['Banana', 'Orange', 'Lemon', 'Apple', 'Mango'];

const sl=fruits.slice(0, 2);
//return//fruits[0], fruits[1];
console.log(sl);//["Banana", "Orange"]

【省略結束値】回報至最末項

const fruits = ['Banana', 'Orange', 'Lemon', 'Apple', 'Mango'];

const sl=fruits.slice(2);
//等於fruits.slice(2, fruits.length);
//return fruits[2], fruits[3], fruits[4]
console.log(sl);//["Lemon", "Apple", "Mango"]

擷取多範圍

fruits共有5項資料。
保留【第1項】【第3~5項】。

多條件要用中括號[]包起來。
條件之間用逗號, 連結。
並且加上spread operator轉換成array形式。

const fruits = ['Banana', 'Orange', 'Lemon', 'Apple', 'Mango'];

const sl=[
 ...fruits.slice(0, 1),
 ...fruits.slice(2)
];

console.log(sl);
//["Banana", "Lemon", "Apple", "Mango"]

陣列相關方法2

Ref: JavaScript30

some()  確認有沒有人符合條件

給予條件→找出是否有一個人(或以上)符合條件。

//共用陣列資料
const people = [
 { name: 'Wes', year: 1988 },
 { name: 'Kait', year: 1986 },
 { name: 'Irv', year: 1970 },
 { name: 'Lux', year: 2015 }
];

確認是否有人已滿19歳

/*****【方法一】function*************/
const isAdult1=people.some(

function(person){
 //getFullYear?
 const currentYear=new Date().getFullYear();
 if(currentYear-person.year>=19){
 return true;
 }
}
 
);


/*****【方法二】arrow function*******/
const isAdult2=people.some(person => new Date().getFullYear()-person.year>=19);
console.log(isAdult2);//true

every()  確認是否全員符合條件

給予條件→找出是否所有人都符合條件。

確認是否全員都滿19歳

const everyAdult=people.every(person => new Date().getFullYear()-person.year>=19);

console.log(everyAdult);//false

find()  找出特定的項目

//共用陣列資料
const comments = [
 { text: 'Love this!', id: 523423 },
 { text: 'Super good', id: 823423 },
 { text: 'You are the best', id: 2039842 },
 { text: 'Ramen is my fav food ever', id: 123523 },
 { text: 'Nice Nice Nice!', id: 542328 }
];

找出id為【823423】的留言

const comment=comments.find(comment => comment.id===823423);

console.log(comment);
/**
Object {
  id: 823423,
  text: "Super good"
}
**/

findIndex 找出特定項目的陣列編號

【※注意】陣列編號由 0 開始算。編號 0 為第1項、編號 1 為第 2 項。

找出id為【823423】的陣列編號

const index=comments.findIndex(comment => comment.id===823423);

console.log(index);//1 ←comments[1];

splice 刪除特定編號的陣列

參考文章
【※注意】只能指定編號刪除。

comments.splice(1, 1);

//然後comments就會少一項了。但不保留沒刪除前的内容。

slice 摘取陣列

參考文章

也可以達到刪除特定項目陣列的效果。
但方法是擷取保留項目,像是指定列印頁數。

共5頁的資料→只列印【第1頁】、【第3-5頁】→最後印出來的資料就會少了第2頁

用slice可以同時保留刪除前的陣列與刪除後的陣列。

const newComments=[
 ...comments.slice(0, 1),//only return comments[0]
 ...comments.slice(2)//omit end value
 //保留slice[0], slice[2-4]→等於去掉slice[1]
]; 

console.log(newComments);
/**
const newC=[
 ...comments.slice(0, 1),//only return comments[0]
 ...comments.slice(2)//omit end value
];
**/

 

搜尋器

Reference: JavaScript 30

先備知識

範例

form.search-form
 input.search(type='text', placeholder='City or State')
 ul.suggestions
 li Filter for a city
 li or a state
html
 box-sizing: border-box
 background: #ffc600
 font-family: 'helvetica neue'
 font-size: 20px
 font-weight: 200

*
 box-sizing: inherit
 &:before, &:after
 box-sizing: inherit

input
 width: 100%
 padding: 20px

.search-form
 max-width: 400px
 margin: 50px auto

input.search
 margin: 0
 text-align: center
 outline: 0
 border: 10px solid #F7F7F7
 width: 120%
 left: -10%
 position: relative
 top: 10px
 z-index: 2
 border-radius: 5px
 font-size: 40px
 box-shadow: 0 0 5px rgba(0, 0, 0, 0.12), inset 0 0 2px rgba(0, 0, 0, 0.19)

.suggestions
 margin: 0
 padding: 0
 position: relative
 /*perspective:20px;
 li
 background: white
 list-style: none
 border-bottom: 1px solid #D8D8D8
 box-shadow: 0 0 10px rgba(0, 0, 0, 0.14)
 margin: 0
 padding: 20px
 transition: background 0.2s
 display: flex
 justify-content: space-between
 text-transform: capitalize
 &:nth-child(even)
 transform: perspective(100px) rotateX(3deg) translateY(2px) scale(1.001)
 background: linear-gradient(to bottom, #ffffff 0%, #EFEFEF 100%)
 &:nth-child(odd)
 transform: perspective(100px) rotateX(-3deg) translateY(3px)
 background: linear-gradient(to top, #ffffff 0%, #EFEFEF 100%)

span.population
 font-size: 15px

.details
 text-align: center
 font-size: 15px

.hl
 background: #ffc600

.love
 text-align: center

a
 color: black
 background: rgba(0, 0, 0, 0.1)
 text-decoration: none
const endpoint = 'https://gist.githubusercontent.com/Miserlou/c5cd8364bf9b2420bb29/raw/2bf258763cdddd704f8ffd3ea9a3e81d25e2c6f6/cities.json';

const cities=[];

//blob用來指稱原始資料
//轉換成JSON資料
fetch(endpoint)
 .then(blob => blob.json())
 .then(data => cities.push(...data))

function find(word, cities){
 
 return cities.filter(place => {
 const regex=new RegExp(word, "gi");
 return place.city.match(regex) || place.state.match(regex)
 })

}

function commas(x){
 return x.toString().replace(/B(?=(d{3})+(?!d))/g, ",");
}


function display(){
 const match=find(this.value, cities);
 const html=match.map(place => {
 const regex=new RegExp(this.value, "gi");
 const cityName=place.city.replace(regex, `<span class="hl">${this.value}</span>`);
 const stateName=place.state.replace(regex, `<span class="hl">${this.value}</span>`);
 
 return `
 <li>
 <span class="name">${cityName}, ${stateName}</span>
 <span class="population">${commas(place.population)}</span>
 </li>
 `
 }).join("");
 
 
 suggestions.innerHTML=html;
}

const search=document.querySelector(".search");
const suggestions=document.querySelector(".suggestions");


search.addEventListener("change", display);
search.addEventListener("keyup", display);

Display

CODEPEN

regex【i】不區別大小寫

功能

讓字串不區分大小寫,也能通過正規表達式檢査。
應用實例:搜尋不用區分大小寫,也能找到同樣的内容。

類似的東西:regex【g】找出所有

範例

方法一【/i】

i代表 insensitive ,對大小寫不敏感。

const regex=/apple/i;

const str1="APPLE";
const str2="Apple";
const str3="apple";

str1.match(regex);//["APPLE"]
str2.match(regex);//["Apple"]
str3.match(regex);//["apple"]
//不論大小寫(或混用)都能通過驗證

方法二【new RegExp】

使用時機:需要檢査的字串不是固定値,而是變數時(像是使用者鍵入的搜尋字詞)

寫法跟【/i】不同,但是效果相同。

const regex=new RegExp("apple", "i");

const str1="APPLE";
const str2="Apple";
const str3="apple";

str1.match(regex);//["APPLE"]
str2.match(regex);//["Apple"]
str3.match(regex);//["apple"]

fetch

介紹

fetch,是ajax之外取得外部資料的一種方式
使用promise物件架構

補充:ajax

範例

取得圖片

img#img
const moko="https://scontent-tpe1-1.xx.fbcdn.net/v/t1.0-9/15355572_1410266889006372_4764767806658769471_n.jpg?oh=39e4da3e9a9956f349b6c701556544ff&oe=593B04D9"


fetch(moko)
 .then(blob => blob.blob())
 .then(myb => img.src=URL.createObjectURL(myb))

//fetch發送請求
//使用blob獲得圖片的內容
//再從blob獲得URL,放到img的src之中

Display

取得JSON資料(有function的情況下)

button#btn click me
ul#ul
const endpoints="https://awiclass.monoame.com/api/command.php?type=get&name=tododata";

const tododata=[];

fetch(endpoints)
 .then(blob => blob.json())
 .then(data => tododata.push(...data))//要用spread把nodeList轉成array

function show (){
 ul.innerHTML=tododata.map(place => `<li>${place.name}</li>`).join("");//join("")把陣列轉換成字串,並且將分隔取代為無
}

btn.addEventListener("click", show)

Display

CODEPEN

取得JSON資料(無function的情況下)

沒有function的情況下,JS無法用內接陣列的方式,讀取外部URL的JSON。
因此,方法改寫如下,

ul#ul
const endpoints="https://awiclass.monoame.com/api/command.php?type=get&name=tododata";

// const tododata=[];

fetch(endpoints)
 .then(blob => blob.json())
 .then(data => {
 ul.innerHTML=data.map(place => `<li>${place.name}</li>`).join("");
})

Display

CODPEN

onclick

功能

onclick 是 HTML 的屬性,等於 JavaScript 的 click 事件

範例

button#btn(onclick="scale()") click me
/******
onclick="scale()"等於這個
btn.addEventListener("click", scale);
******/

function scale(){
 btn.style.transform="scale(2)";
 btn.style.transformOrigin="left top";
}

Display

jQuery AJAX

功能

不刷新頁面也能完成前後端的溝通。

  • HTML + PHP + MySQL :必須刷新頁面
  • HTMP + Ajax + PHP + MySQL:不必刷新頁面

以 Facebook 的按讚功能為例
使用者按讚→資料庫更新讚數→網頁顯示讚數+1

透過 Ajax 的輔助,這一連串的動作都可以不刷新網頁而達成。
使用者不必中斷滑到一半的閱讀體驗。

結構

  • form tag,使用 HTTP Method
  • 引入 jQuery
  • 準備處理資料庫溝通的 php 檔案,跟 HTML 放在同一個資料夾底下
  • 準備好 MySQL 資料表

範例

前端

//HTTP Method
form(method="POST")
 input(type="text" placeholder="email")
 input(type="password" placeholder="password")
 input(type="submit")
 p
const email=document.querySelectorAll("input")[0];
const password=document.querySelectorAll("input")[1];
const submit=document.querySelectorAll("input")[2];
const alert=document.querySelector("p");


submit.addEventListener("click", function(e){
 //clear submit default
 e.preventDefault();
 $.ajax({
 type: "POST",//對應 form 的 method
 url: "insert.php",//指定 php 檔案
 data: {
 //要傳送的值。php超全域變數: js變數
 email: email.value, 
 password: password.value
 },
 //若傳送成功
 success: function(re){
 //若php回應值=="success"
 if(re=="success"){
 alert.textContent="register successful"
 }else{
 alert.textContent="register fail"
 }
 }
 });
})

後端

+----------+------+-----+---------------+
| Field    | Type | Key | Extra         |
+----------+------+-----+---------------+
| ID       | int  | PRI | auto_increment|
| email    | text |     |               |
| password | text |     |               |
+----------+------+-----+---------------+
<?php

mysql_connect("localhost", "root", "");//MySQL連線
mysql_select_db("register");//選擇資料庫

//$_POST['email']與$_POST['password']是ajax定義好的超全域變數
//防止隱碼注入攻擊
$_POST['email']=mysql_escape_string($_POST['email']);
$_POST['password']=mysql_escape_string($_POST['password']);

//帳號與密碼寫入資料庫
$save=mysql_query("INSERT INTO member (email, password) VALUES('$_POST[email]','$_POST[password]')");


if(!$save){
 
 echo "fail";//若寫入失敗回傳fail

}else{
 
 echo "success";//成功則回傳success
}


?>

 

match() 與 test()

功能

兩者都是搭配正規表達式的Method

match()

字串.match(正規表達式);
//回傳比對出的部分字串結果
//若比對不到東西,則回傳null

const str="apple";
const check=/[a-c]/;

str.match(check);//["a"]

test()

正規表達式.test(字串);
//有比對到回傳true,沒有則回傳false

const str="apple";
const check=/[a-c]/;

check.test(str);//true

setInterval

功能

設定每隔一段時間執行一次動作,可以模擬動畫效果
缺點:只能處理能夠數值化的CSS屬性(width, opacity, scale, rotate等等),不能完成顏色漸變等效果

button(onclick="show()") click me
br
br
#box show up
#box
 width: 200px
 height: 100px
 background-color: #aaffe2
 text-align: center
 line-height: 100px
function show(){
 //pos是變動的透明度數值
 var pos=0;
 //setInterval(動作, 毫秒)→每隔幾毫秒,執行一次動作
 const id=window.setInterval(function(){
 
 if(pos>=1){
 //如果pos夠大了,清掉Interval,阻止繼續增加下去
 clearInterval(id);
 pos=0;
 }else{
 pos+=0.1;
 box.style.opacity=pos;
 }
 
 }, 50)
}

show up

正規表達式

説明

正規表達式,是專門用來處理字串邏輯的條件語法。
常常用在 input 表單上,檢査使用者輸入的内容有沒有符合特定格式。
諸如信用卡號格式驗證,密碼強度驗證等等。

相關方法:match(),test()

語法

宣告正規表達式

正規表達式的前後要各用1個斜線/包起來(有點像字串需要用”包起來)。

const str="abcde";
const check=/abc/;

str.match(cri);//["abc"]
//回傳 被找到的字串
if(str.match(cri)){
 console.log(true);
 //↑通常驗證表單會寫成這個形式,裡面放通過驗證後的動作
}else{
 console.log(false)
}//true

尋找某區間内文字

設定檢索區間: [a-e]
a為開始値,e為終止値。

const str="apple";
const check=/[a-c]/;//尋找有沒有 a,b,c 這3個字母
//※寫成/[a-c]/g會比較好(下面會説明)
//※也可以比對多重區間(下面説明)

str.match(check)//["a"]

if(str.match(cri)){
 console.log(true);
}else{
 console.log(false);
}//true ←但只有 a 有被找到而已,pple並沒有被找到

/*****注意*****
※這個例子match是寬鬆尋找方法,只要有1個字元被找到,就會跑出true
*************/

尋找全部【/g】

參考這篇文章

一般情況下,match只會1次比對一個字串字元而已,所以回傳値只會吐出第1項符合的字元。
如果要叫他吐出所有,就要使用/g←放在正規表達式的最後面

※但如果是用 if(str.match(check)) 的話不使用 /g 也沒差

const str="apple";
const check=/[a-e]/;
const check2=/[a-e]/g;//尋找全部寫法


str.match(check);//["a"]
str.match(check2);//["a", "e"]←這樣寫就可以一條一條找出所有符合的字元了

/****比對多重區間***/
const check3=/[a-eo-p]/g//←直接接下去加入就好

str.match(check3);//["a", "p", "p", "e"]

尋找數字與數字字符【\d】

有2種方法可以找到數字

  • 設定 0-9 的區間
  • 正規字符【\d】
const str="apple123";
const check=/[0-9]/g
const check2=/\d/g; //\d效果同等於[0-9]


str.match(check);//["1", "2", "3"]
str.match(check2);//["1", "2", "3"]

檢査數字長度

使用大括號{}指定該字元必須出現幾次

const str="123456";
const check=/\d{4}/;//{4}表示數字(\d)要連續出現4次←4位數字

str.match(check);//["1234"]

if(str.match(check)){
 console.log(true);
}else{
 console.log(false);
}//ture
/*****
str="123456"→true
str="1234"→true
str="12"→false

只有當str不足4位數字時才會回報false(match找不到匹配値,回傳null)
str滿足或超過4位數都會回傳true
※那要怎麼寫手機號碼驗證!←下面告訴你
*****/

設定嚴格的條件:使用開始【^】與結束【$】字符

在上例中可以發現,只有當字串不滿足條件時才會報 false ,剛好符合或超過條件的清況都會回報 true 。

但某些情況下,我們需要非常精確的格式,比如

  • 手機號碼必須是 10 位數字
  • 信用卡號必為 4位數-4位數-4位數-4位數
  • 身份證字號為 1大寫英文 + 9 為數字

所以我們必須更進階地規定條件,不足或超出條件者都必須是 false。

以下為嚴格化寫法

  • 在開頭加入開始字符^
  • 結尾加入結束字符$
//驗證手機號碼
const str1="0912345678";
const str2="0912";
const str3="09123456789999"
const check=/^\d{10}$/;

str1.match(check); //["0912345678"]
str2.match(check); //null
str3.match(check); //null

//進階:規定前2碼為09

const check2=/^09\d{8}$/; //指定09跟後面的8碼條件不用任何東西連接,直接加上就行

str1.match(check2); //["0912345678"]

英數字集符號【\w】

如同 \d 為 [0-9] 的簡寫一般,
\w 則為 [a-zA-Z0-9_] 的簡寫。

可以檢查所有英文大小寫字母,底線,以及數字

const str="ianchen_0419";

const check=/\w/g;
const check2=/[a-zA-Z0-9_]/g;
//這兩個效果一樣

str.match(check);//["i", "a", "n", "c", "h", "e", "n", "_", "0", "4", "1", "9"]
str.match(check2);//["i", "a", "n", "c", "h", "e", "n", "_", "0", "4", "1", "9"]

 

可有可無的條件【?】

『使用者名稱可以包含減號-』

這樣的情況下,不論字串有無減號-,皆可滿足條件。
因此減號-是一個可有可無的條件。

在減號-後面加上問號 ? ,便可以構成可有可無的驗證式

const str1="ianchen";
const str2="ian-chen";
const str3="ian--chen";
const str4="-ianchen";
const str5="ianchen-";

const check=/\w-?\w/;
const check2=/^\w-?\w$/;
//用check2的話5個str都會是null,因為check2必須是【1字母 + 有無底線皆可 + 1字母】的組合才行。


str1.match(check);//["ia"]
str2.match(check);//["ia"]
str3.match(check);//["ia"]
str4.match(check);//["-i"]
str5.match(check);//["ia"]
//結論:5種情況都可以true

比對特殊字元:前置反斜線\

Regex特殊字元【.^$?】

這些字元都是Regex特殊字符,帶有特殊的意義。
若要單純比對這些符號,必須前置反斜線\以做區別。

const str1="$";
const str2="^";
const str3=".";
const str4="?";

const check1=/\$/;
const check2=/\^/;
const check3=/\./;
const check4=/\?/;

str1.match(check1);//["$"]
str2.match(check2);//["^"]
str3.match(check3);//["."]
str4.match(check4);//["?"]

比對很多次【+】

重複比對【+】前面的東西很多次

const str="ianchen_0419";
const check=/\w+/;
//重複比對英文字元(←【+】前面的)很多次,直到比對不了為止

str.match(check);//["ianchen_0419"]

有後面再比對前面【?=】

a(?=b)→如果有找到b的話,再檢查前面有沒有a,並且比對出a
【?=b】要使用括號包起來

const str1="ianchen0419";
const str2="ianchen"
const str3="0419ianchen";
const check=/[a-z]+(?=\d)/;
//後面有數字的話,再比對前面有沒有英文
//然後把所有的英文都找出來【+】

str1.match(check);//["ianchen"]
str2.match(check);//null ←這個因為後面找不到數字所以沒辦法比對英文
str3.match(check);//null ←這個因為數字前面沒有英文所以一樣無法

沒有後面再比對前面【?!】

a(?!b)→如果沒有找到b的話,再檢查前面有沒有a,並且比對出a
【?!b】要使用括號包起來

使用情境:給數字加上分隔逗號,每3碼加一個逗號,但是要從最末位往前數(所以要確保後面沒有落單的數字)

【例】1,234,567,890

const str1="ianchen0419";
const str2="ianchen"
const str3="0419ianchen";
const check=/[a-z]+(?!\d)/;
//後面沒有數字的話,再比對前面有沒有英文
//然後把所有的英文都找出來【+】

str1.match(check);//null ←這個因為後面有數字了所以不能比對前面的英文
str2.match(check);//["ianchen"]
str3.match(check);//["ianchen"]

找出英文邊界【\b】

※前提,英文句子要用空格隔開單字

【\b\w】→找到每個單字字首
【\w\b】→找到每個單字字尾

const str="this is a apple";//必須要有空格
const check1=/\b\w/g;//找字首
const check2=/\w\b/g;//找字尾

str.match(check1);//["t", "i", "a", "a"]
//"this is a apple"

str.match(check2);//["s", "s", "a", "e"]
//"this is a apple"

找出非英文邊界【\B】

用途:數字,符號等等

const str="1234567890";
const check1=/\B\d{3}/g;//每3個字斷開(從後面開始數)
const check2=/\d{3}\B/g;//每3個字斷開(從前面開始數)


str.match(check1);//["234", "567", "890"]
str.match(check2);//["123", "456", "789"]

英文數字逗點

【例】1234567890→1,234,567,890

function commas(x){
 return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
 //①從最末位往前數3碼(後面沒有數字的話,比對出3個數字)
 //②一直數一直數
 //③符合①就給他分隔符號
 //④\B比對非英文字的邊界,如果沒有\B的話會變成",123,456,789"
 //取代所有分隔符號為","

}

線上練習網站:RegexOne

JavaScript Array methods 陣列相關方法1

本篇介紹的method

filter()[👧, 👦, 👩, 👨, 👵, 🧓] => [👦, 👨, 🧓]原本的陣列有 6 個,後來變成 3 個
map()[👧, 👦, 👩, 👨, 👵, 🧓] => [👧🏿, 👦🏿, 👩🏿, 👨🏿, 👵🏿, 🧓🏿]陣列內容都變成不一樣的顏色了(但數量一樣是 6 個)
sort()[👧, 👦, 👩, 👨, 👵, 🧓] => [👧, 👩, 👵 👦, 👨, 🧓]陣列的順序改變了
reduce()[👧, 👦, 👩, 👨, 👵, 🧓] => 👨‍👩‍👧陣列被綜合成一個東西了

本篇使用到的共用物件

const inventors = [
 { first: 'Albert', last: 'Einstein', year: 1879, passed: 1955 },
 { first: 'Isaac', last: 'Newton', year: 1643, passed: 1727 },
 { first: 'Galileo', last: 'Galilei', year: 1564, passed: 1642 },
 { first: 'Marie', last: 'Curie', year: 1867, passed: 1934 },
 { first: 'Johannes', last: 'Kepler', year: 1571, passed: 1630 },
 { first: 'Nicolaus', last: 'Copernicus', year: 1473, passed: 1543 },
 { first: 'Max', last: 'Planck', year: 1858, passed: 1947 },
 { first: 'Katherine', last: 'Blodgett', year: 1898, passed: 1979 },
 { first: 'Ada', last: 'Lovelace', year: 1815, passed: 1852 },
 { first: 'Sarah E.', last: 'Goode', year: 1855, passed: 1905 },
 { first: 'Lise', last: 'Meitner', year: 1878, passed: 1968 },
 { first: 'Hanna', last: 'Hammarström', year: 1829, passed: 1909 }
 ];

filter() 把陣列的數量變少,但是內容不變

filter() 裡面放一個函數,filter() 可以物件→物件,也能夠陣列→陣列

應用:篩選出出生於 16 世紀的投資客(出生年介於 1500 到 1600 之間)

const arr1=inventors.filter(function(item){
 if(item.year>=1500 && item.year<1600){
  return true;
 }
})

console.log(arr1); //[{ first: 'Galileo', last: 'Galilei', year: 1564, passed: 1642 }, { first: 'Johannes', last: 'Kepler', year: 1571, passed: 1630 }]

ES6 應用:箭頭函數(省略 return

const arr1=inventors.filter(item => item.year>=1500 && item.year<1600);

console.log(arr1); //[{ first: 'Galileo', last: 'Galilei', year: 1564, passed: 1642 }, { first: 'Johannes', last: 'Kepler', year: 1571, passed: 1630 }]

map() 陣列數量固定,但裡面內容置換

map() 可以物件→陣列,也可以陣列→陣列

應用:列出所有投資客的全名(first + last

const arr1=inventors.map(item => `${item.first} ${item.last}`);

console.log(arr1); //["Albert Einstein", "Isaac Newton", "Galileo Galilei", "Marie Curie", "Johannes Kepler", "Nicolaus Copernicus", "Max Planck", "Katherine Blodgett", "Ada Lovelace", "Sarah E. Goode", "Lise Meitner", "Hanna Hammarström"]

sort() 更動陣列順序

sort() 可以物件→物件,也能夠陣列→陣列
  • return 1:往後移動
  • return -1:往前移動

應用①:將投資客由老排到年輕(出生年數字越大代表越年輕,所以要越往後移動)

sort() 裡面的函數可以塞兩個參數,第一個值代表比較的第一個人、第二個值代表比較的第二個人,sort() 會幫忙把所有排列組合都列出來
const arr1=inventors.sort(function(a, b){
 if(a.year>b.year){
  return 1; //如果a的年紀比b小,a往後擺
 }else{
  return -1;
 }
});

console.table(arr1); //結果會出來一個由老排到小的物件

ES6 應用:箭頭函數 + if 簡寫

const arr1=inventors.sort((a, b) => a.year>b.year ? 1 : -1)

console.table(arr1);

應用②:將投資客的名字 (firstname) 開頭字母由A排到Z

const arr1=inventors.sort((a, b) => a.first>b.first ? 1 : -1)

console.table(arr1);

reduce() 將陣列內容輸出為單一值

reduce() 可以物件→單一值,也可以陣列→單一值

應用①:計算所有投資客總共活了多久

  • reduce() 裡面的函數的第一個參數為「累加器 accumulator」,第二個參數為「迭代中的元素 currentValue
  • reduce() 裡面的第二個值為一個數字,表示「累加器初始值 initialValue」(通常填 0
const value=inventors.reduce(function(total, item){
 return total += (item.passed-item.year);
}, 0)

console.log(value); //861

ES6 應用:箭頭函數

const value=inventors.reduce((total, item) => total += (item.passed-item.year), 0)

console.log(value); //861

應用②:統計各個交通工具出現的次數

//原始資料
const data = ['car', 'car', 'truck', 'truck', 'bike', 'walk', 'car', 'van', 'bike', 'walk', 'car', 'van', 'car', 'truck' ];
var value=data.reduce((obj, item)=>{
 obj[item]++;
 return obj;
},{
 car: 0,
 walk: 0,
 truck: 0,
 bike: 0,
 van: 0
})

console.log(value); // {bike: 2, car: 5, truck: 3, van: 2, walk: 2}

符號分割字串 split()

字串的分割

split() 裡面填入一個字串形式的值,這個值用來切割其他要拿來切割的字串
var str='2020/01/01';
console.log(str.split('/')); //["2020", "01", "01"]

陣列=>字串的分割

陣列可以搭配 map() 轉換成字串在分割,但分割後的格式會變成陣列中的陣列
var arr=['2020/01/01 15:00', '2020/04/04 13:00', '2020/03/03 09:00'];
var arr2=arr.map(item => item.split(' '));
console.log(arr2); //[["2020/01/01", "15:00"], ["2020/04/04", "13:00"], ["2020/03/03", "09:00"]]
或乾脆只抓取分割後的某部分,組成新的陣列
var arr=['2020/01/01 15:00', '2020/04/04 13:00', '2020/03/03 09:00'];
var arr2=arr.map(item => {
 var [date, time]=item.split(' ');
 return date;
})
console.log(arr2); //["2020/01/01", "2020/04/04", "2020/03/03"]

shift() 切出第一個字串

var str="2020/01/01"
console.log(str.split('/').shift()); //"2020"

pop() 切出最後一個字串

var str="2020/01/01"
console.log(str.split('/').pop()); //"01"

檢查陣列是否包含特定值 includes()

includes() 可以用來檢查陣列是否包含某個值,回傳值為布林值
var arr=['2020/01/01', '2020/01/02', '2020/01/03'];

console.log(arr.includes('2020/01/01')) //true

結合 filter() 篩出特定值組合成一個新陣列

var arr=['2020/01/01', '2020/01/02', '2020/01/03', '2020/02/01', '2020/02/02', '2020/02/03'];

var arr2=arr.filter(item => item.includes('2020/01'));
console.log(arr2); //["2020/01/01", "2020/01/02", "2020/01/03"]

nodeList轉array

querySelectorAll()抓取下來的物件,雖然看起來跟array很像,但他其實是nodeList,所以不能直接適用於array可以用的一大堆好用method,例如map()reduce()等等
ul
 li apple
 li banana
 li grape

ES6法 展開運算符 Spread syntax

Spread syntax是ES6提供的方法,只要用[...]就能夠轉換了
var mylist=document.querySelectorAll('li');
var newlist=[...mylist];

傳統法 Array.from()

如果瀏覽器較舊不支援ES6 [...]大法的話,也能改用傳統的Array.from()
雖然不比ES6法簡潔,但也算方便
var mylist=document.querySelectorAll('li');
var newlist=Array.from(mylist);

console 相關method介紹

console系列除了開發抓蟲最常用的console.log()以外,還存在著許多便利的method,幫助在開發中更有智慧的抓蟲

字詞置換 %s

%ssstring的意思,可以做到在噴出除錯訊息時做到字詞置換的效果

var a='apple';
console.log('hello, I want a %s', a);

樣式變更 %c

在多人開發的環境中,console log時常會被上百個訊息給淹沒,這時可以透過%c自訂顯目的樣式讓他在很多訊息之中脫穎而出

var style='font-size: 60px; background: red; color: white';
console.log('%cImportant!', style);

%c如果放在訊息開頭的地方表示他從最頭開始就要套上醒目樣式,如果%c宣告在訊息中間的話則代表從指定的位置開始再做醒目提示

警告/錯誤/提示訊息

這三種在一般開發上都蠻少見,但在別人做好的套件上會常常看到

警告訊息 console.warn()

console.warn()用來提示警告性的訊息,在Chrome上的樣式是驚嘆號三角形+黃色底的造型

console.warn('我是警告訊息');

錯誤訊息 console.error()

console.error()用來提示錯誤性的訊息,在Chrome上的樣式是圓形叉叉+紅色底的造型

console.error('我是錯誤訊息');

提示訊息 console.info()

console.info()居然被新版的Chrome移除了,他現在用起來跟console.log()無異

驗證 console.assert()

console.assert(1===2);

第二個值可填可不填,填了如果出錯時他會幫你噴出自訂的錯誤訊息
(填了第二個值的範例:console.assert(1===2, '錯了');

清除 console.clear()

用了就會幫你把上面的歷史紀錄都清掉

檢視屬性 console.dir()

他可以一口氣噴出指定元素的所有屬性,像是offsetTopinnerHTMLparentNode、以及綁在他身上的各種事件

console.dir(document.body);

群組化呈現 console.group()

這也是給多人開發時使用的便利工具,尤其是在同一頁面同時噴出好幾百個log令人無法招架時,可以用console.group()將訊息歸類分群組

console.group('===登入頁測試區===');
console.log('假log1');
console.log('假log2');
console.log('假log3');
console.groupEnd();

console.group('===首頁測試區===');
console.log('假log1');
console.log('假log2');
console.log('假log3');
console.groupEnd();

上面的console.group()預設是將所有訊息展開,如果怕他太亂也可以試試看console.groupCollapsed(),是預設收合的樣式

console.groupCollapsed('===登入頁測試區 測試已完成===');
console.log('假log1');
console.log('假log2');
console.log('假log3');
console.groupEnd();

console.group('===首頁測試區===');
console.log('假log1');
console.log('假log2');
console.log('假log3');
console.groupEnd();

計算次數 console.count()

console.count()原意是計算次數,但可以巧妙的把它塞進某些function之中,觀察該函式被使用的次數(看執行效能?)

const arr=[1,3,5,7,9];
arr.reduce((total, number)=>{
 console.count();
 return total+=number;
},0);

reduce()看起來只是一個簡單的method而已,寫起來也不過幾行,沒想到一放入console.count()就發現他默默地被執行了5

計算時間 console.time()

console.time()可以計算一個動作執行時所花費的時間,藉以觀察效能
由於一般程式執行速度都很快,所以這邊用一個跟外部檔案抓資料的fetch()範例來看看花費時間

console.time("抓下資料");
fetch("https://gist.githubusercontent.com/ianchen0419/d499e572044655814f38aaf56c779823/raw/9876407c12b1e6bc5c31fe6e9f875ccfb82af43b/schedule.json")
 .then(data => data.json())
 .then(data => {
  console.timeEnd("抓下資料");
  console.log(data);
});

陣列+物件資料用表格呈現 console.table()

console.table()可以把一坨難看懂的陣列物件複合資料用表格呈現,可以一目瞭然增加易讀性

fetch("https://gist.githubusercontent.com/ianchen0419/d499e572044655814f38aaf56c779823/raw/9876407c12b1e6bc5c31fe6e9f875ccfb82af43b/schedule.json")
 .then(data => data.json())
 .then(data => {
  console.table(data);
});

dataset

dataset是一個方便取用元素屬性的規範
基本上元素屬性的命名是任意,但在dataset的規範下,會命名成以data-開頭的名稱,例如

#div(data-color="green" data-size="20")

如此一來,可以用強大的dataset物件快速取得屬性值

div.dataset.color; //'green'

div.dataset.size; //'20'

針對不能用dataset的瀏覽器(例如很舊的IE),就只能使用傳統的getAttribute()

div.getAttribute('data-color'); //'green'

div.getAttribute('data-size'); //'20'

Date()

  • 抓現在是西元幾年 new Date().getFullYear()
  • 抓現在是幾點 new Date().getHours()
  • 抓現在是幾分 new Date().getMinutes()
  • 抓現在是幾秒 new Date().getSeconds()

顯示現在時間

function runTime(){
 var now=new Date();
 var seconds=now.getSeconds();
 var mins=now.getMinutes();
 var hours=now.getHours();
 console.log(`現在是${hours}時${mins}分${seconds}秒`);
}
//每一秒偵測一次時間
setInterval(runTime, 1000);

箭頭函數

箭頭函數是ES6新規範,旨在將method裡面呼叫的functions做出字面上的簡化,例如

window.addEventListener('keydown', function(e){
 console.log(e.keyCode);
})

上面這段程式碼表示在畫面上按下任何一個按鍵,會回傳該按鍵的keyCode
經過箭頭函式簡化後,可以變成這樣

window.addEventListener('keydown', e=>console.log(e.keyCode));

簡化後,他跟第一版程式具有一樣的功能

簡化進程詳解

首先,箭頭函式其實只是把function拿掉,並在(){}之間加了一個箭頭=>以代表他其實是一個函式而已

window.addEventListener('keydown', (e)=>{console.log(e.keyCode);})

但是,()裡面只有一個參數而已,{}裡面也只有一行程式,實在沒有框起來的必要
所以當()的參數只有一位時可以省略括弧
而當{}的程式只有一行時,也能省略大括弧

window.addEventListener('keydown', e=>console.log(e.keyCode));

經過最終進化後成了超簡明版本

與陣列的關係

箭頭函數經常跟陣列相關的method搭配使用,例如nodeList的新式迴圈forEach()、陣列計算的reduce()、陣列整形的map()

forEach() 範例

forEach()可以代替jQuery$()簡便的抓取nodeList並且做出迴圈處理

ul
 li apple
 li grape
 li banana
const mylist=document.querySelectorAll('ul li');
mylist.forEach(item => console.log(item.textContent))

//apple
//grape
//banana

reduce() 範例

const arr=[1,3,5,7,9];
arr.reduce((total,number)=>{
 return total+=number;
},0)

map() 範例

const arr=['apple', 'grape', 'banana'];
arr.map(item=>`GOOD ${item}`);
//(3) ["GOOD apple", "GOOD grape", "GOOD banana"]

綁定多種事件時的做法

畫面上有3個按鈕,每個按鈕都要各榜clickmouseover事件,一樣可以用同一個forEach()寫,各個事件之間用分號;隔開

button click
button click
button click
const btns=document.querySelectorAll("button");
btns.forEach((btn, n) => {
  btn.addEventListener("click", function(){
   console.log(`you clicked btn ${n+1}`);
 });
  btn.addEventListener("mousemove", function(){
   console.log(`you touched btn ${n+1}`);
 })
})

keyCode

鍵盤上的每個鍵都有自己的識別代碼,稱為keyCode
在實作key相關事件時,keyCode是不可或缺的情報

網頁查詢keyCode

KeyCode Info

進入上述網站後,隨便按下想要查詢的按鍵,畫面就能顯示出該案件的keyCode

JavaScript查詢keyCode

window.addEventListener("keydown",
 function(e){
  console.log(e.keyCode);
  //console.log(e.which);
 }
);

打開網頁,隨便按下一個案件,就能看到開發者工具回傳該案件的keyCode代碼
除了e.keyCode可以查到代碼外,e.which也能夠查到代碼。

定位三劍客:scrollTop、offsetTop、clientTop

scrollTop:滑動多少的距離

#outer
 #inner
#outer
 height: 500px
 border: 1px solid #000
 overflow-y: scroll
 
#inner
 height: 800px
 background: teal

上述範例在滑動到最底時,外層的#outer一共是滑動了300px,因為#outer高度只有500px,但內層的#innter卻有800px,外層不夠的300px就會變成滑動的距離。當滑到最底部時,一共滑了300px

網頁整體的拉霸

若想取得現在整體網頁拉霸滑動到哪裡的位置,可以用以下JS

document.documentElement.scrollTop

如果用了document.body.scrollTop不論滑動到哪裡都會回傳0,所以必須用documentElement(實測Chrome/IE/FireFox/Safari)

offsetTop:本體相對於offsetParent的距離

offsetTop的指涉的概念大概就像上圖這樣
offsetParent所在的位置可以透過targetElement.offsetParent來查詢

offsetParent就是擁有position: relative定位的母層元素
如果我把上圖中的<article>的相對定位屬性移除,offsetParent會再找到更上面擁有相對定位的母層
如果把上面所有相對定位都掃掉,offsetParent就會回傳<body>

※例外:當目標元素為position: fixed時,由於他是相對於視窗做定位,不存在什麼絕對母層,所以他的offsetParent會回傳null

padding / border 會不會影響到offsetTop呢?
由於padding / border 都是屬於上圖咖啡色方塊的目標元素之中
所以他們不會影響到offsetTop

clientTop:簡單來說就是border-top

但其實clientTop指涉的不是border-top,他是指元素外層與內層之間的距離。
如果把OS設定為阿拉伯文 / 希伯來文這種拉霸在左邊的語言,就可以看出端倪

這時的clientLeft會加上拉霸的15px,所以總和值為50px+15px=65px

參考資料

要素サイズとスクローリング

JS動畫 requestAnimationFrame

前端動畫

在前端領域裡,一共有幾種方式可以處理動畫

  • CSS animation 參考舊文
  • CSS transition
  • SVG SMIL <animate> 參考舊文
  • JavaScript setInterval
  • JavaScript setTimeout
  • JavaScript requestAnimationFrame

requestAnimationFrame是一個專門用來處理動態的方法,相對於前5項作法,其用法也相當複雜(但同時也很精細)
注意他不支援IE9(含)以下

RAF(略)用法初探

requestAnimationFrame的用法跟setInterval / setTimeout有一點點很像
這3種動畫函數都是window呼叫(並且都能省略window)
塞的第一個值都必須是動作的function
不過,setInterval / setTimeout都要指定第二個值:「秒數」
然後RAF不用塞秒數,所以我們只要寫成這樣就好▼

window.requestAnimationFrame(function(timestamp){
 console.log(timestamp)
})

注意RAF塞進去的第一個值:函數的裡面
我又在塞了一個callback name:timestamp
這個callback name名字隨意取就行,不叫timestamp也可以

這個timestamp代表什麼。我們開啟開發者工具看看console.log(timestamp)的回傳結果

timestamp回傳的結果是「毫秒」

第一個值15147.488毫秒,換算成正常秒數約等於15秒。代表我打開這個網頁後,等了大約15秒後,我才打開了開發面板,鍵入了window.requestAnimationFrame(...)這坨函數

第二個值16281.32毫秒,約等於16秒。等於網頁準備好後過了16秒,window.request...(...)才被執行(所以他跟第一個console相距1秒)

但是,實務上執行動畫時不能夠像這樣一直key函數上去,所以需要改寫成循環函數,讓RAF可以不斷地被重複執行

function call(timestamp){
 console.log(timestamp);
 window.requestAnimationFrame(call);
}

window.requestAnimationFrame(call);

【補充說明】
想要抓到RAF的index時,可以這樣做

function call(timestamp){
 var idx=window.requestAnimationFrame(call);
 console.log(idx);
}

window.requestAnimationFrame(call);

實作動態

本次實作利用rgb參數調整的方式
做出動態的變色效果

  • b值最小為0
  • b值最大為255

⑴ 用canvas畫一個圓

canvas#canvas(width=500 height=500)
canvas
 border: 1px solid #000
 width: 250px
 height: 250px
var ctx=canvas.getContext('2d');
ctx.beginPath();
ctx.arc(250, 250, 100, 0, 2*Math.PI, false);
ctx.fill();

⑵ 加入RAF函數

定義每毫秒colorIndex就會跑一次

  • colorIndex加到255時,反向遞減
  • colorIndex減到0時,又變成遞增
var colorIndex=0;
var isPlus=true;

function call(timestamp){
 if(colorIndex==255){
  isPlus=false;
 }
 
 if(colorIndex==0){
  isPlus=true;
 }
 
 if(isPlus==true){
  colorIndex++;
 }else{
  colorIndex--;
 }
 
 ctx.fillStyle=`rgb(0, 150, ${colorIndex})`;
 ctx.fill();
 
 window.requestAnimationFrame(call);
}

window.requestAnimationFrame(call);

⑶ RAF的停止

停止運作要用到cancelAnimationFrame這個方法
用法如下

cancelAnimationFrame(★這裏填入要停下的RAF★)

★要停下的RAF★可以用變數的方式預先儲存好
完整範例如下

canvas#canvas(width=500 height=500)
button(onclick="stopAnimation()") STOP IT
canvas
 border: 1px solid #000
 width: 250px
 height: 250px
var ctx=canvas.getContext('2d');
ctx.beginPath();
ctx.arc(250, 250, 100, 0, 2*Math.PI, false);
ctx.fill();

var colorIndex=0;
var isPlus=true;
var myAnimation;

function call(timestamp){
 if(colorIndex==250){
  isPlus=false;
 }
 
 if(colorIndex==0){
  isPlus=true;
 }
 
 if(isPlus==true){
  colorIndex++;
 }else{
  colorIndex--;
 }
 
 ctx.fillStyle=`rgb(0, 150, ${colorIndex})`;
 ctx.fill();
 
 myAnimation=window.requestAnimationFrame(call);
}

window.requestAnimationFrame(call);

function stopAnimation(){
 window.cancelAnimationFrame(myAnimation);
}

選單hover效果:Magic Line

效果說明

這是一個選單的hover特效,本來底線會放在現在訪問的那一頁的選單項目之下,當滑鼠碰到某個其他項目時,那條底線就會滑過去

做法詳解

首先,做一個簡單的頁首選單

header
 a.logo(href="javascript") LOGO
 nav
  a(href="javascript").visited 首頁
  a(href="javascript") 服務項目
  a(href="javascript") 產品一覽
  a(href="javascript") 關於我們
  a(href="javascript") 聯絡表單
  #line

這裡假定現在造訪的位置在「首頁」,所以給「首頁」掛了一個.visited
並且可以看到,提示的線不是一個固定的border,他是一個叫做#line的元素,要假裝成border掛在底下而已
之後會隨著JS設定而飄來飄去

header
 display: flex
 justify-content: space-between
 padding: 0 20px
 background: #333
 border-radius: 4px

a
 text-decoration: none
 color: #fff
 padding: 20px 0

nav
 position: relative
 
nav *
 margin: 0 10px
 display: inline-block

#line
 margin: 0
 position: absolute
 height: 2px
 background: #fff
 bottom: 10px
 left: 0
 width: 32px
 transition-duration: 0.3s
 border-radius: 2px

JS詳解⑴ 抓取visited的固定位置

現在我們還沒法看到魔法底線,所以先讓他顯示出來
注意假如造訪了首頁,底線會出現在「首頁」底下
但如果又造訪了關於我們,底線會出現在「關於我們底下」

所以底線的長度位置都不能用CSS寫死,必須用JS動態設定才行

const visitedMenu=document.querySelector('.visited');
const allMenu=document.querySelectorAll('nav a');
const nav=document.querySelector('nav');

line.style.width=`${visitedMenu.clientWidth}px`;
line.style.left=`${visitedMenu.offsetLeft}px`;

這裡使用clientWidth抓取寬度,offsetLeft抓取該選單項目與左邊邊界的距離
注意由於<a>預設的display: inline之下,回傳的clientWidth會為0
(畢竟inline元素有寬度也是件奇怪的事情)
所以必須將<a>改成display: inline-block

JS詳解⑵ 動態抓到每個hover的位置

接著,來實作當滑鼠滑到別的選單項目時,底線移動(與改變長度)的效果
因為JS沒有hover這個事件,所以改成用mouseover來實作滑鼠移入

我們在原本的JS程式碼下方加入這段:

allMenu.forEach(item => item.onmouseover=function(){
 line.style.width=`${this.clientWidth}px`;
 line.style.left=`${this.offsetLeft}px`;
})

這時我們的小白線就能隨著滑鼠位置而動來動去了
不過,本範例中的visited位置在首頁,我今天滑到了「關於我們」後
小白線就直接停留在「關於我們」下面了,不會回到「首頁」下面
這樣邏輯不通,所以要在補寫一個mouseout事件,讓他在滑來滑去結束後
回到原本visited的位置

nav.onmouseout=function(){
 line.style.width=`${visitedMenu.clientWidth}px`;
 line.style.left=`${visitedMenu.offsetLeft}px`;
}

這裡雖然直覺上會想要把mouseout事件掛在allMenu
但看倌們仔細想想,我們這些選單項目<a>都有設定margin啥的
滑鼠非常容易就滑出去<a>,然後小白線就會像中風一樣一直想要回到visited的位置
因此,本例將此還原事件掛在更為全面的<nav>上面,以防小白線動得太靈敏了

JS詳解⑶ 函式化整理程式碼

到此為止,功能已經做好了
只是看看我們亂七八糟的JS程式碼

const visitedMenu=document.querySelector('.visited');
const allMenu=document.querySelectorAll('nav a');
const nav=document.querySelector('nav');

line.style.width=`${visitedMenu.clientWidth}px`;
line.style.left=`${visitedMenu.offsetLeft}px`;

allMenu.forEach(item => item.onmouseover=function(){
 line.style.width=`${this.clientWidth}px`;
 line.style.left=`${this.offsetLeft}px`
})

nav.onmouseout=function(){
 line.style.width=`${visitedMenu.clientWidth}px`;
 line.style.left=`${visitedMenu.offsetLeft}px`;
}

可以看到很多line.style....的東西一直在重複
這裡也是可以做簡化的,我們再把它寫成進階的函式整理成以下

const visitedMenu=document.querySelector('.visited');
const allMenu=document.querySelectorAll('nav a');
const nav=document.querySelector('nav');

function lineMove(target){
 line.style.width=`${target.clientWidth}px`;
 line.style.left=`${target.offsetLeft}px`
}

// line.style.width=`${visitedMenu.clientWidth}px`;
// line.style.left=`${visitedMenu.offsetLeft}px`;
lineMove(visitedMenu);

allMenu.forEach(item => item.onmouseover=function(){
 // line.style.width=`${this.clientWidth}px`;
 // line.style.left=`${this.offsetLeft}px`
 lineMove(this);
})

nav.onmouseout=function(){
 // line.style.width=`${visitedMenu.clientWidth}px`;
 // line.style.left=`${visitedMenu.offsetLeft}px`;
 lineMove(visitedMenu);
}

簡化後,不到20行JS就能搞定!

參考資料:https://css-tricks.com/jquery-magicline-navigation/