[JS] 常用 JavaScript snippets
UI 相關
將捲軸到該元素最下方 (scroll to bottom)
const scrollToBottom = (targetElement) => {
targetElement.scrollTo({
top: targetElement.scrollHeight,
behavior: 'smooth',
});
};
避免按鈕點選後卡在 focus 狀態
keywords: prevent button focus when click
使用 e.currentTarget.blur()
。
<button id="prevent-focus">FooBar</button>
const btn = document.querySelector('#prevent-focus');
btn.addEventListener('click', (e) => e.currentTarget.blur());
如果只使用
e.target.blur()
有可能當點選按鈕中的圖案時,因為target
指稱到的是裡面的圖案,所以會無效。
取得鼠標座標(需檢查有效性)
function mousePosition(event) {
if (event.pageX || event.pageY) {
return { x: event.pageX, y: event.pageY };
}
return {
x: event.clientX + document.body.scrollLeft - document.body.clientLeft,
y: event.clientY + document.body.scrollTop - document.body.clientTop,
};
}
取得座標相關資料(需檢查有效性)
檔案可視區域寬: document.documentElement.clientWidth
檔案可視區域高: document.documentElement.clientHeight
網頁可見區域寬: document.body.clientWidth
網頁可見區域高: document.body.clientHeight
網頁可見區域寬: document.body.offsetWidth (包括邊線的寬)
網頁可見區域高: document.body.offsetHeight (包括邊線的高)
網頁正文全文寬: document.body.scrollWidth
網頁正文全文高: document.body.scrollHeight
網頁被捲去的高: document.body.scrollTop
網頁被捲去的左: document.body.scrollLeft
網頁正文部分上: window.screenTop
網頁正文部分左: window.screenLeft
螢幕分辨率的高: window.screen.height
螢幕分辨率的寬: window.screen.width
螢幕可用工作區高度: window.screen.availHeight
螢幕可用工作區寬度: window.screen.availWidth
取得瀏覽器捲軸位置
let scrollOffset = window.scrollY || document.documentElement.scrollTop;
window.scrollY @ MDN
Lazy Load
相關內容可參考:Preload Content 內容預先載入(Lazy Load)
font lazy load
可以監聽字體載入的事件,當字體載入完成後,再 放入 CSS 的 font-family 內,如此可以避免一開始畫面沒有文字的情況:
// font lazy load
document.fonts.load('12px Noto Sans TC').then(function () {
const body = document.querySelector('body');
body.style.fontFamily = `"Noto Sans TC", ${window
.getComputedStyle(body)
.getPropertyValue('font-family')}`;
});
顏色相關 Color Related
RGB To Hex
const RGBToHex = (r, g, b) => ((r << 16) + (g << 8) + b).toString(16).padStart(6, '0');
RGB to Hex @ 30 seconds of code
Random Hex Color
const randomHexColorCode = () => {
let n = (Math.random() * 0xfffff * 1000000).toString(16);
return '#' + n.slice(0, 6);
};
Random Hex Color @ 30 seconds of code
檔案相關 File Related
格式化上傳檔案大小
const prettyBytes = (num, precision = 3, addSpace = true) => {
const UNITS = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
if (Math.abs(num) < 1) return num + (addSpace ? ' ' : '') + UNITS[0];
const exponent = Math.min(Math.floor(Math.log10(num < 0 ? -num : num) / 3), UNITS.length - 1);
const n = Number(((num < 0 ? -num : num) / 1000 ** exponent).toPrecision(precision));
return (num < 0 ? '-' : '') + n + (addSpace ? ' ' : '') + UNITS[exponent];
};
prettyBytes(1000); // "1 KB"
prettyBytes(-27145424323.5821, 5); // "-27.145 GB"
prettyBytes(123456789, 3, false); // "123MB"
Pretty Bytes @ 30 seconds of code
限制上傳檔案類型
<!-- 當今瀏覽器 -->
<input type="file" name="filePath" accept=".jpg,.jpeg,.doc,.docx,.pdf" />
<!-- 限制圖片 -->
<input type="file" class="file" value="上傳" accept="image/*" />
好用工具(Utilities)
pause / sleep
const pause = (ms) => {
let time = new Date();
while (new Date() - time <= ms);
};
text truncate
// DEMO:
// https://repl.it/@PJCHENder/TextTruncate-Function
var truncate = function (elem, limit, after) {
// Make sure an element and number of items to truncate is provided
if (!elem || !limit) return;
// Get the inner content of the element
var content = elem.textContent.trim();
// Convert the content into an array of words
// Remove any words above the limit
content = content.split(' ').slice(0, limit);
// Convert the array of words back into a string
// If there's content to add after it, add it
content = content.join(' ') + (after ? after : '');
// Inject the content back into the DOM
elem.textContent = content;
};
debounce
throttle 和 debounce 的差別在於,throttle 指的是在「一定時間」內最多只能促發某函式「多少次」;debounce 則是指在「一定時間」內能夠促發這個函式「一次」。
/**
* 將要 debounce 的函式代入 func 中,不用 invoke
* wait,設定時間(毫秒)
* immediate 如果是 true 則在事件觸發時會立即執行,
* 但事件結束時不會執行;如果是 false 則事件觸發時不會立即執行,
* 但事件結束時會執行。
**/
export function debounce(callback, wait, immediate) {
let timeout;
function wrapper(...args) {
const self = this;
const later = () => {
timeout = null;
if (!immediate) callback.apply(self, args);
};
const callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) callback.apply(this, args);
}
return wrapper;
}
The Difference between Throttling and Debouncing @ CSS Tricks Debouncing and Throttling Explained Through Examples @ CSS Tricks
在一般的情況下使用
/**
* 一秒內只會觸發一次 click 內的 callback
**/
const btn = document.querySelector('#btn');
btn.addEventListener(
'click',
debounce(
(e) => {
console.log('click', e);
},
1000,
true,
),
);
在 Vue 中使用
// 將 debounce function 放在 Vue Instance 外面
function debounce () { ... }
new Vue({
methods: {
wantThisDebounce: debounce(function(){
// Put what you want to do here
}, 1500, false)
}
})
throttle
export function throttle(callback, limit) {
let wait = false;
// DONOT change to arrow function, otherwise 'this' can not be reset
function wrapper(...args) {
if (!wait) {
callback.apply(this, args);
wait = true;
setTimeout(() => {
wait = false;
}, limit);
}
}
return wrapper;
}
debounce and throttle example
HTML
<button id="debounce">debounce</button> <button id="throttle">throttle</button>
JS
const debounceBtn = document.querySelector('#debounce');
const throttleBtn = document.querySelector('#throttle');
class Pet {
constructor(animal) {
this.animal = animal;
}
meow() {
console.log(`${this.animal} is meow`);
}
}
const dog = new Pet('dog');
debounceBtn.addEventListener('click', debounce(log('debounce'), 1000, false));
throttleBtn.addEventListener('click', throttle(log('debounce'), 1000));
/* debounceBtn.addEventListener("click", debounce(dog.meow, 1000, false).bind(dog));
throttleBtn.addEventListener("click", throttle(dog.meow, 1000).bind(dog));
*/
let count = 0;
function log(message) {
return (e) => {
console.log('message', message);
e.preventDefault();
console.log(count++);
};
}
function throttle(callback, limit) {
let wait = false;
function wrapper(...args) {
if (!wait) {
callback.apply(this, args);
wait = true;
setTimeout(() => {
wait = false;
}, limit);
}
}
return wrapper;
}
function debounce(callback, wait, immediate) {
let timeout;
function wrapper(...args) {
const self = this;
const later = function () {
timeout = null;
if (!immediate) {
callback.apply(self, args);
}
};
const callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) {
callback.apply(this, args);
}
}
return wrapper;
}
See the Pen camel case and snake case transfer by PJCHEN (@PJCHENder) on CodePen.
camel case, snake case 的轉換
export const camelize = (text) => {
return text.replace(/^([A-Z])|[\s-_]+(\w)/g, function (match, p1, p2) {
if (p2) return p2.toUpperCase();
return p1.toLowerCase();
});
};
export const decamelize = (str, separator) => {
separator = typeof separator === 'undefined' ? '_' : separator;
return str
.replace(/([a-z\d])([A-Z])/g, '$1' + separator + '$2')
.replace(/([A-Z]+)([A-Z][a-z\d]+)/g, '$1' + separator + '$2')
.replace(/([\d]+)/g, separator + '$1')
.toLowerCase();
};
export const snakeToCamelCase = (o) => {
if (typeof o === 'string') {
return camelize(o);
} else if (typeof o === 'object') {
return Object.fromEntries(
Object.entries(o).map(([key, value]) => {
if (value && typeof value === 'object') {
return [camelize(key), snakeToCamelCase(value)];
}
return [camelize(key), value];
}),
);
}
};
export const camelCaseToSnake = (o) => {
if (typeof o === 'string') {
return decamelize(o);
} else if (typeof o === 'object') {
return Object.fromEntries(
Object.entries(o).map(([key, value]) => {
if (value && typeof value === 'object') {
return [decamelize(key), camelCaseToSnake(value)];
}
return [decamelize(key), value];
}),
);
}
};
See the Pen camel case and snake case transfer by PJCHEN (@PJCHENder) on CodePen.
型別判斷
typeof true; // 'boolean'
typeof 'foobar'; // 'string'
typeof 123; // 'number'
typeof NaN; // 'number'
typeof {}; // 'object'
typeof []; // 'object'
typeof undefined; // 'undefined'
typeof window.alert; // 'function'
typeof null; // 'object'
判斷式否為陣列
let arr = [];
Array.isArray(arr);
判斷是否為函式(function)
if (typeof v === 'function') {
// do something
}
Check if a variable is of function type @ StackOverflow
判斷型別與值是否相同(equality comparisons)
- Equality comparisons and sameness @ MDN
- Object.is() @ MDN
時間相關
時間倒數計時
<p id="left-time"></p>
function countdown() {
var endTime = new Date('May 2, 2018 21:31:09');
var nowTime = new Date();
if (nowTime >= endTime) {
document.getElementById('left-time').innerHTML = '倒計時間結束';
return;
}
var leftSecond = parseInt((endTime.getTime() - nowTime.getTime()) / 1000);
if (leftSecond < 0) {
leftSecond = 0;
}
days = parseInt(leftSecond / 3600 / 24);
hours = parseInt((leftSecond / 3600) % 24);
minutes = parseInt((leftSecond / 60) % 60);
seconds = parseInt(leftSecond % 60);
document.getElementById('left-time').innerHTML =
days + '天' + hours + '小時' + minutes + '分' + seconds + '秒';
}
countdown();
setInterval(countdown, 1000);
時間戳記格式化
function formatDate(now) {
var y = now.getFullYear();
var m = now.getMonth() + 1; // 注意 JavaScript 月份+1
var d = now.getDate();
var h = now.getHours();
var m = now.getMinutes();
var s = now.getSeconds();
return y + '-' + m + '-' + d + ' ' + h + ':' + m + ':' + s;
}
var nowDate = new Date(1442978789184);
alert(formatDate(nowDate));