分類彙整:JavaScript

元素位置與座標

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】throw

用法

可以讓console panel噴出錯誤訊息

function count(number){
 if(number<10){
  throw 'number < 10';
 }else {
  throw 'number >= 10';
 }
}

噴出檔案名、行數

function count(number){
 if(number<10){
  throw new Error('number < 10');
 }else {
  throw 'number >= 10';
 }
}

【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】假的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