PJCHENder 未整理筆記

[JS] 常用 JavaScript snippets

2018-05-16

[JS] 常用 JavaScript snippets

[TOC]

UI 相關

將捲軸到該元素最下方 (scroll to bottom)

1
2
3
4
5
6
const scrollToBottom = targetElement => {
targetElement.scrollTo({
top: targetElement.scrollHeight,
behavior: 'smooth',
});
};

避免按鈕點選後卡在 focus 狀態

keywords: prevent button focus when click

使用 e.currentTarget.blur()

1
2
3
<button id='prevent-focus'>
FooBar
</button>
1
2
const btn = document.querySelector('#prevent-focus');
btn.addEventListener('click', (e) => e.currentTarget.blur());

如果只使用 e.target.blur() 有可能當點選按鈕中的圖案時,因為 target 指稱到的是裡面的圖案,所以會無效。

取得鼠標座標(需檢查有效性)

1
2
3
4
5
6
7
8
9
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
};
}

取得座標相關資料(需檢查有效性)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
檔案可視區域寬: 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

取得瀏覽器捲軸位置

1
let scrollOffset = window.scrollY || document.documentElement.scrollTop;

window.scrollY @ MDN

Lazy Load

相關內容可參考:[Preload Content 內容預先載入(Lazy Load)](/Users/pjchen/Projects/Note/source/_posts/HTML/[HTML] Preloading Content 內容預先載入(Lazy Load).md)

font lazy load

可以監聽字體載入的事件,當字體載入完成後,再放入 CSS 的 font-family 內,如此可以避免一開始畫面沒有文字的情況

1
2
3
4
5
// 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')}`;
});

RGB To Hex

1
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

1
2
3
4
const randomHexColorCode = () => {
let n = (Math.random() * 0xfffff * 1000000).toString(16);
return '#' + n.slice(0, 6);
};

Random Hex Color @ 30 seconds of code

格式化上傳檔案大小

1
2
3
4
5
6
7
8
9
10
11
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

限制上傳檔案類型

1
2
3
4
5
<!-- 當今瀏覽器 -->
<input type="file" name="filePath" accept=".jpg,.jpeg,.doc,.docx,.pdf">

<!-- 限制圖片 -->
<input type="file" class="file" value="上傳" accept="image/*"/>

好用工具(Utilities)

pause / sleep

1
2
3
4
const pause = (ms) => {
let time = new Date();
while ((new Date()) - time <= ms);
}

text truncate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 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;

};

How to truncate text with vanilla JavaScript

debounce

throttledebounce 的差別在於,throttle 指的是在「一定時間」內最多只能促發某函式「多少次」;debounce 則是指在「一定時間」內能夠促發這個函式「一次」

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 將要 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

在一般的情況下使用

1
2
3
4
5
/**
* 一秒內只會觸發一次 click 內的 callback
**/
const btn = document.querySelector('#btn');
btn.addEventListener('click', debounce(e => {console.log('click', e);}, 1000, true));

在 Vue 中使用

1
2
3
4
5
6
7
8
9
10
// 將 debounce function 放在 Vue Instance 外面
function debounce () { ... }

new Vue({
methods: {
wantThisDebounce: debounce(function(){
// Put what you want to do here
}, 1500, false)
}
})

JavaScript debounce function @ DWB

throttle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
1
2
<button id="debounce">debounce</button>
<button id="throttle">throttle</button>
JS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
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 的轉換
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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.

### 型別判斷
1
2
3
4
5
6
7
8
9
10
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'

判斷式否為陣列

1
2
let arr = [];
Array.isArray(arr);

判斷是否為函式(function)

1
2
3
if (typeof v === "function") {
// do something
}

Check if a variable is of function type @ StackOverflow

判斷型別與值是否相同(equality comparisons)

js-equality

時間相關

時間倒數計時

1
<p id="left-time"></p>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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);

時間戳記格式化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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));

跨瀏覽器相關(cross browser related)

檢查瀏覽器是否支援 SVG

1
2
3
4
5
6
7
8
9
function isSupportSVG() {
var SVG_NS = 'http://www.w3.org/2000/svg';
return (
!!document.createElementNS &&
!!document.createElementNS(SVG_NS, 'svg').createSVGRect
);
}

console.log(isSupportSVG());

檢查瀏覽器是否支援 canvas

1
2
3
4
5
function isSupportCanvas() {
return (document.createElement('canvas').getContext) ? true : false;
}

console.log(isSupportCanvas());

檢測是否移動端及瀏覽器內核

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var browser = {
versions: function() {
var u = navigator.userAgent;
return {
trident: u.indexOf('Trident') > -1, //IE內核
presto: u.indexOf('Presto') > -1, //opera內核
webKit: u.indexOf('AppleWebKit') > -1, //蘋果、谷歌內核
gecko: u.indexOf('Firefox') > -1, //火狐內核Gecko
mobile: !!u.match(/AppleWebKit.*Mobile.*/), //是否移動終端
ios: !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/), //ios
android: u.indexOf('Android') > -1 || u.indexOf('Linux') > -1, //android
iPhone: u.indexOf('iPhone') > -1, //iPhone
iPad: u.indexOf('iPad') > -1, //iPad
webApp: u.indexOf('Safari') > -1 //Safari
};
}
};

if (
browser.versions.mobile() ||
browser.versions.ios() ||
browser.versions.android() ||
browser.versions.iPhone() ||
browser.versions.iPad()
) {
alert('移動端');
}

檢測是否電腦端/移動端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
var browser = {
versions: (function() {
var u = navigator.userAgent,
app = navigator.appVersion;
var sUserAgent = navigator.userAgent;
return {
trident: u.indexOf('Trident') > -1,
presto: u.indexOf('Presto') > -1,
isChrome: u.indexOf('chrome') > -1,
isSafari: !u.indexOf('chrome') > -1 && /webkit|khtml/.test(u),
isSafari3:
!u.indexOf('chrome') > -1 &&
/webkit|khtml/.test(u) &&
u.indexOf('webkit/5') != -1,
webKit: u.indexOf('AppleWebKit') > -1,
gecko: u.indexOf('Gecko') > -1 && u.indexOf('KHTML') == -1,
mobile: !!u.match(/AppleWebKit.*Mobile.*/),
ios: !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/),
android: u.indexOf('Android') > -1 || u.indexOf('Linux') > -1,
iPhone: u.indexOf('iPhone') > -1,
iPad: u.indexOf('iPad') > -1,
iWinPhone: u.indexOf('Windows Phone') > -1
};
})()
};

if (browser.versions.mobile || browser.versions.iWinPhone) {
window.location = 'http:/www.baidu.com/m/';
};

檢查瀏覽器內核

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function getInternet() {
if (navigator.userAgent.indexOf('MSIE') > 0) {
return 'MSIE'; //IE瀏覽器
}

if ((isFirefox = navigator.userAgent.indexOf('Firefox') > 0)) {
return 'Firefox'; //Firefox瀏覽器
}

if ((isSafari = navigator.userAgent.indexOf('Safari') > 0)) {
return 'Safari'; //Safan瀏覽器
}

if ((isCamino = navigator.userAgent.indexOf('Camino') > 0)) {
return 'Camino'; //Camino瀏覽器
}
if ((isMozilla = navigator.userAgent.indexOf('Gecko/') > 0)) {
return 'Gecko'; //Gecko瀏覽器
}
}

強制手機橫向顯示

1
2
3
4
5
6
7
8
$(window).on('orientationchange', function(event) {
if (event.orientation == 'portrait') {
$('body').css('transform', 'rotate(90deg)');
} else {
$('body').css('transform', 'rotate(0deg)');
}
});
$(window).orientationchange();

參考

Tags: snippet

掃描二維條碼,分享此文章