Browser
arrayToHtmlList
将给定的数组元素转换为 <li>
标签,并将它们添加到给定 id 的列表中。
使用 Array.prototype.map()
、document.querySelector()
和内部闭包创建 html
标签列表。
const arrayToHtmlList = (arr, listID) => (el => ( (el = document.querySelector('#' + listID)), (el.innerHTML += arr.map(item => `<li>${item}</li>`).join('')) ))();
arrayToHtmlList(['item 1', 'item 2'], 'myListID');
bottomVisible
如果页面底部可见,返回 true
,否则返回 false
。 使用 scrollY
, scrollHeight
和 clientHeight
来判断页面的底部是否可见。
const bottomVisible = () => document.documentElement.clientHeight + window.scrollY >= (document.documentElement.scrollHeight || document.documentElement.clientHeight);
bottomVisible(); // true
copyToClipboard
⚠️ NOTICE: The same functionality can be easily implemented by using the new asynchronous Clipboard API, which is still experimental but should be used in the future instead of this snippet. Find out more about it here.
Copy a string to the clipboard. Only works as a result of user action (i.e. inside a click
event listener).
Create a new <textarea>
element, fill it with the supplied data and add it to the HTML document. Use Selection.getRangeAt()
to store the selected range (if any). Use document.execCommand('copy')
to copy to the clipboard. Remove the <textarea>
element from the HTML document. Finally, use Selection().addRange()
to recover the original selected range (if any).
const copyToClipboard = str => { const el = document.createElement('textarea'); el.value = str; el.setAttribute('readonly', ''); el.style.position = 'absolute'; el.style.left = '-9999px'; document.body.appendChild(el); const selected = document.getSelection().rangeCount > 0 ? document.getSelection().getRangeAt(0) : false; el.select(); document.execCommand('copy'); document.body.removeChild(el); if (selected) { document.getSelection().removeAllRanges(); document.getSelection().addRange(selected); } };
copyToClipboard('Lorem ipsum'); // 'Lorem ipsum' copied to clipboard.
counter
Creates a counter with the specified range, step and duration for the specified selector.
Check if step
has the proper sign and change it accordingly. Use setInterval()
in combination with Math.abs()
and Math.floor()
to calculate the time between each new text draw. Use document.querySelector().innerHTML
to update the value of the selected element. Omit the fourth parameter, step
, to use a default step of 1
. Omit the fifth parameter, duration
, to use a default duration of 2000
ms.
const counter = (selector, start, end, step = 1, duration = 2000) => { let current = start, _step = (end - start) * step < 0 ? -step : step, timer = setInterval(() => { current += _step; document.querySelector(selector).innerHTML = current; if (current >= end) document.querySelector(selector).innerHTML = end; if (current >= end) clearInterval(timer); }, Math.abs(Math.floor(duration / (end - start)))); return timer; };
counter('#my-id', 1, 1000, 5, 2000); // Creates a 2-second timer for the element with id="my-id"
createElement
Creates an element from a string (without appending it to the document). If the given string contains multiple elements, only the first one will be returned.
Use document.createElement()
to create a new element. Set its innerHTML
to the string supplied as the argument. Use ParentNode.firstElementChild
to return the element version of the string.
const createElement = str => { const el = document.createElement('div'); el.innerHTML = str; return el.firstElementChild; };
const el = createElement( `<div class="container"> <p>Hello!</p> </div>` ); console.log(el.className); // 'container'
createEventHub
Creates a pub/sub (publish–subscribe) event hub with emit
, on
, and off
methods.
Use Object.create(null)
to create an empty hub
object that does not inherit properties from Object.prototype
. For emit
, resolve the array of handlers based on the event
argument and then run each one with Array.prototype.forEach()
by passing in the data as an argument. For on
, create an array for the event if it does not yet exist, then use Array.prototype.push()
to add the handler to the array. For off
, use Array.prototype.findIndex()
to find the index of the handler in the event array and remove it using Array.prototype.splice()
.
const createEventHub = () => ({ hub: Object.create(null), emit(event, data) { (this.hub[event] || []).forEach(handler => handler(data)); }, on(event, handler) { if (!this.hub[event]) this.hub[event] = []; this.hub[event].push(handler); }, off(event, handler) { const i = (this.hub[event] || []).findIndex(h => h === handler); if (i > -1) this.hub[event].splice(i, 1); if (this.hub[event].length === 0) delete this.hub[event]; } });
const handler = data => console.log(data); const hub = createEventHub(); let increment = 0; // Subscribe: listen for different types of events hub.on('message', handler); hub.on('message', () => console.log('Message event fired')); hub.on('increment', () => increment++); // Publish: emit events to invoke all handlers subscribed to them, passing the data to them as an argument hub.emit('message', 'hello world'); // logs 'hello world' and 'Message event fired' hub.emit('message', { hello: 'world' }); // logs the object and 'Message event fired' hub.emit('increment'); // `increment` variable is now 1 // Unsubscribe: stop a specific handler from listening to the 'message' event hub.off('message', handler);
currentURL
Returns the current URL.
Use window.location.href
to get current URL.
const currentURL = () => window.location.href;
currentURL(); // 'https://google.com'
detectDeviceType
Detects whether the website is being opened in a mobile device or a desktop/laptop.
Use a regular expression to test the navigator.userAgent
property to figure out if the device is a mobile device or a desktop/laptop.
const detectDeviceType = () => /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ? 'Mobile' : 'Desktop';
detectDeviceType(); // "Mobile" or "Desktop"
elementContains
Returns true
if the parent
element contains the child
element, false
otherwise.
Check that parent
is not the same element as child
, use parent.contains(child)
to check if the parent
element contains the child
element.
const elementContains = (parent, child) => parent !== child && parent.contains(child);
elementContains(document.querySelector('head'), document.querySelector('title')); // true elementContains(document.querySelector('body'), document.querySelector('body')); // false
elementIsVisibleInViewport
Returns true
if the element specified is visible in the viewport, false
otherwise.
Use Element.getBoundingClientRect()
and the window.inner(Width|Height)
values to determine if a given element is visible in the viewport. Omit the second argument to determine if the element is entirely visible, or specify true
to determine if it is partially visible.
const elementIsVisibleInViewport = (el, partiallyVisible = false) => { const { top, left, bottom, right } = el.getBoundingClientRect(); const { innerHeight, innerWidth } = window; return partiallyVisible ? ((top > 0 && top < innerHeight) || (bottom > 0 && bottom < innerHeight)) && ((left > 0 && left < innerWidth) || (right > 0 && right < innerWidth)) : top >= 0 && left >= 0 && bottom <= innerHeight && right <= innerWidth; };
// e.g. 100x100 viewport and a 10x10px element at position {top: -1, left: 0, bottom: 9, right: 10} elementIsVisibleInViewport(el); // false - (not fully visible) elementIsVisibleInViewport(el, true); // true - (partially visible)
formToObject
Encode a set of form elements as an object
.
Use the FormData
constructor to convert the HTML form
to FormData
, Array.from()
to convert to an array. Collect the object from the array, using Array.prototype.reduce()
.
const formToObject = form => Array.from(new FormData(form)).reduce( (acc, [key, value]) => ({ ...acc, [key]: value }), {} );
formToObject(document.querySelector('#form')); // { email: 'test@email.com', name: 'Test Name' }
getImages
Fetches all images from within an element and puts them into an array
Use Element.prototype.getElementsByTagName()
to fetch all <img>
elements inside the provided element, Array.prototype.map()
to map every src
attribute of their respective <img>
element, then create a Set
to eliminate duplicates and return the array.
const getImages = (el, includeDuplicates = false) => { const images = [...el.getElementsByTagName('img')].map(img => img.getAttribute('src')); return includeDuplicates ? images : [...new Set(images)]; };
getImages(document, true); // ['image1.jpg', 'image2.png', 'image1.png', '...'] getImages(document, false); // ['image1.jpg', 'image2.png', '...']
getScrollPosition
Returns the scroll position of the current page.
Use pageXOffset
and pageYOffset
if they are defined, otherwise scrollLeft
and scrollTop
. You can omit el
to use a default value of window
.
const getScrollPosition = (el = window) => ({ x: el.pageXOffset !== undefined ? el.pageXOffset : el.scrollLeft, y: el.pageYOffset !== undefined ? el.pageYOffset : el.scrollTop });
getScrollPosition(); // {x: 0, y: 200}
getStyle
Returns the value of a CSS rule for the specified element.
Use Window.getComputedStyle()
to get the value of the CSS rule for the specified element.
const getStyle = (el, ruleName) => getComputedStyle(el)[ruleName];
getStyle(document.querySelector('p'), 'font-size'); // '16px'
hasClass
Returns true
if the element has the specified class, false
otherwise.
Use element.classList.contains()
to check if the element has the specified class.
const hasClass = (el, className) => el.classList.contains(className);
hasClass(document.querySelector('p.special'), 'special'); // true
hashBrowser
Creates a hash for a value using the SHA-256 algorithm. Returns a promise.
Use the SubtleCrypto API to create a hash for the given value.
const hashBrowser = val => crypto.subtle.digest('SHA-256', new TextEncoder('utf-8').encode(val)).then(h => { let hexes = [], view = new DataView(h); for (let i = 0; i < view.byteLength; i += 4) hexes.push(('00000000' + view.getUint32(i).toString(16)).slice(-8)); return hexes.join(''); });
hashBrowser(JSON.stringify({ a: 'a', b: [1, 2, 3, 4], foo: { c: 'bar' } })).then(console.log); // '04aa106279f5977f59f9067fa9712afc4aedc6f5862a8defc34552d8c7206393'
hide
Hides all the elements specified.
Use NodeList.prototype.forEach()
to apply display: none
to each element specified.
const hide = (...el) => [...el].forEach(e => (e.style.display = 'none'));
hide(document.querySelectorAll('img')); // Hides all <img> elements on the page
httpsRedirect
Redirects the page to HTTPS if its currently in HTTP. Also, pressing the back button doesn't take it back to the HTTP page as its replaced in the history.
Use location.protocol
to get the protocol currently being used. If it's not HTTPS, use location.replace()
to replace the existing page with the HTTPS version of the page. Use location.href
to get the full address, split it with String.prototype.split()
and remove the protocol part of the URL.
const httpsRedirect = () => { if (location.protocol !== 'https:') location.replace('https://' + location.href.split('//')[1]); };
httpsRedirect(); // If you are on http://mydomain.com, you are redirected to https://mydomain.com
insertAfter
Inserts an HTML string after the end of the specified element.
Use el.insertAdjacentHTML()
with a position of 'afterend'
to parse htmlString
and insert it after the end of el
.
const insertAfter = (el, htmlString) => el.insertAdjacentHTML('afterend', htmlString);
insertAfter(document.getElementById('myId'), '<p>after</p>'); // <div id="myId">...</div> <p>after</p>
insertBefore
Inserts an HTML string before the start of the specified element.
Use el.insertAdjacentHTML()
with a position of 'beforebegin'
to parse htmlString
and insert it before the start of el
.
const insertBefore = (el, htmlString) => el.insertAdjacentHTML('beforebegin', htmlString);
insertBefore(document.getElementById('myId'), '<p>before</p>'); // <p>before</p> <div id="myId">...</div>
Recommended Resource - JavaScript: The Hard Parts
Take your JavaScript to the next level. Gain an understanding of callbacks, higher order functions, closure, asynchronous and object-oriented JavaScript!
isBrowserTabFocused
Returns true
if the browser tab of the page is focused, false
otherwise.
Use the Document.hidden
property, introduced by the Page Visibility API to check if the browser tab of the page is visible or hidden.
const isBrowserTabFocused = () => !document.hidden;
isBrowserTabFocused(); // true
nodeListToArray
Converts a NodeList
to an array.
Use spread operator inside new array to convert a NodeList
to an array.
const nodeListToArray = nodeList => [...nodeList];
nodeListToArray(document.childNodes); // [ <!DOCTYPE html>, html ]
observeMutations
Returns a new MutationObserver and runs the provided callback for each mutation on the specified element.
Use a MutationObserver
to observe mutations on the given element. Use Array.prototype.forEach()
to run the callback for each mutation that is observed. Omit the third argument, options
, to use the default options (all true
).
const observeMutations = (element, callback, options) => { const observer = new MutationObserver(mutations => mutations.forEach(m => callback(m))); observer.observe( element, Object.assign( { childList: true, attributes: true, attributeOldValue: true, characterData: true, characterDataOldValue: true, subtree: true }, options ) ); return observer; };
const obs = observeMutations(document, console.log); // Logs all mutations that happen on the page obs.disconnect(); // Disconnects the observer and stops logging mutations on the page
off
Removes an event listener from an element.
Use EventTarget.removeEventListener()
to remove an event listener from an element. Omit the fourth argument opts
to use false
or specify it based on the options used when the event listener was added.
const off = (el, evt, fn, opts = false) => el.removeEventListener(evt, fn, opts);
const fn = () => console.log('!'); document.body.addEventListener('click', fn); off(document.body, 'click', fn); // no longer logs '!' upon clicking on the page
on
Adds an event listener to an element with the ability to use event delegation.
Use EventTarget.addEventListener()
to add an event listener to an element. If there is a target
property supplied to the options object, ensure the event target matches the target specified and then invoke the callback by supplying the correct this
context. Returns a reference to the custom delegator function, in order to be possible to use with off
. Omit opts
to default to non-delegation behavior and event bubbling.
const on = (el, evt, fn, opts = {}) => { const delegatorFn = e => e.target.matches(opts.target) && fn.call(e.target, e); el.addEventListener(evt, opts.target ? delegatorFn : fn, opts.options || false); if (opts.target) return delegatorFn; };
const fn = () => console.log('!'); on(document.body, 'click', fn); // logs '!' upon clicking the body on(document.body, 'click', fn, { target: 'p' }); // logs '!' upon clicking a `p` element child of the body on(document.body, 'click', fn, { options: true }); // use capturing instead of bubbling
onUserInputChange
Run the callback whenever the user input type changes (mouse
or touch
). Useful for enabling/disabling code depending on the input device. This process is dynamic and works with hybrid devices (e.g. touchscreen laptops).
Use two event listeners. Assume mouse
input initially and bind a touchstart
event listener to the document. On touchstart
, add a mousemove
event listener to listen for two consecutive mousemove
events firing within 20ms, using performance.now()
. Run the callback with the input type as an argument in either of these situations.
const onUserInputChange = callback => { let type = 'mouse', lastTime = 0; const mousemoveHandler = () => { const now = performance.now(); if (now - lastTime < 20) (type = 'mouse'), callback(type), document.removeEventListener('mousemove', mousemoveHandler); lastTime = now; }; document.addEventListener('touchstart', () => { if (type === 'touch') return; (type = 'touch'), callback(type), document.addEventListener('mousemove', mousemoveHandler); }); };
onUserInputChange(type => { console.log('The user is now using', type, 'as an input method.'); });
prefix
Returns the prefixed version (if necessary) of a CSS property that the browser supports.
Use Array.prototype.findIndex()
on an array of vendor prefix strings to test if document.body
has one of them defined in its CSSStyleDeclaration
object, otherwise return null
. Use String.prototype.charAt()
and String.prototype.toUpperCase()
to capitalize the property, which will be appended to the vendor prefix string.
const prefix = prop => { const capitalizedProp = prop.charAt(0).toUpperCase() + prop.slice(1); const prefixes = ['', 'webkit', 'moz', 'ms', 'o']; const i = prefixes.findIndex( prefix => typeof document.body.style[prefix ? prefix + capitalizedProp : prop] !== 'undefined' ); return i !== -1 ? (i === 0 ? prop : prefixes[i] + capitalizedProp) : null; };
prefix('appearance'); // 'appearance' on a supported browser, otherwise 'webkitAppearance', 'mozAppearance', 'msAppearance' or 'oAppearance'
recordAnimationFrames
Invokes the provided callback on each animation frame.
Use recursion. Provided that running
is true
, continue invoking window.requestAnimationFrame()
which invokes the provided callback. Return an object with two methods start
and stop
to allow manual control of the recording. Omit the second argument, autoStart
, to implicitly call start
when the function is invoked.
const recordAnimationFrames = (callback, autoStart = true) => { let running = true, raf; const stop = () => { running = false; cancelAnimationFrame(raf); }; const start = () => { running = true; run(); }; const run = () => { raf = requestAnimationFrame(() => { callback(); if (running) run(); }); }; if (autoStart) start(); return { start, stop }; };
const cb = () => console.log('Animation frame fired'); const recorder = recordAnimationFrames(cb); // logs 'Animation frame fired' on each animation frame recorder.stop(); // stops logging recorder.start(); // starts again const recorder2 = recordAnimationFrames(cb, false); // `start` needs to be explicitly called to begin recording frames
redirect
Redirects to a specified URL.
Use window.location.href
or window.location.replace()
to redirect to url
. Pass a second argument to simulate a link click (true
- default) or an HTTP redirect (false
).
const redirect = (url, asLink = true) => asLink ? (window.location.href = url) : window.location.replace(url);
redirect('https://google.com');
runAsync
Runs a function in a separate thread by using a Web Worker, allowing long running functions to not block the UI.
Create a new Worker
using a Blob
object URL, the contents of which should be the stringified version of the supplied function. Immediately post the return value of calling the function back. Return a promise, listening for onmessage
and onerror
events and resolving the data posted back from the worker, or throwing an error.
const runAsync = fn => { const worker = new Worker( URL.createObjectURL(new Blob([`postMessage((${fn})());`]), { type: 'application/javascript; charset=utf-8' }) ); return new Promise((res, rej) => { worker.onmessage = ({ data }) => { res(data), worker.terminate(); }; worker.onerror = err => { rej(err), worker.terminate(); }; }); };
const longRunningFunction = () => { let result = 0; for (let i = 0; i < 1000; i++) for (let j = 0; j < 700; j++) for (let k = 0; k < 300; k++) result = result + i + j + k; return result; }; /* NOTE: Since the function is running in a different context, closures are not supported. The function supplied to `runAsync` gets stringified, so everything becomes literal. All variables and functions must be defined inside. */ runAsync(longRunningFunction).then(console.log); // 209685000000 runAsync(() => 10 ** 3).then(console.log); // 1000 let outsideVariable = 50; runAsync(() => typeof outsideVariable).then(console.log); // 'undefined'
scrollToTop
Smooth-scrolls to the top of the page.
Get distance from top using document.documentElement.scrollTop
or document.body.scrollTop
. Scroll by a fraction of the distance from the top. Use window.requestAnimationFrame()
to animate the scrolling.
const scrollToTop = () => { const c = document.documentElement.scrollTop || document.body.scrollTop; if (c > 0) { window.requestAnimationFrame(scrollToTop); window.scrollTo(0, c - c / 8); } };
scrollToTop();
serializeForm
Encode a set of form elements as a query string.
Use the FormData
constructor to convert the HTML form
to FormData
, Array.from()
to convert to an array, passing a map function as the second argument. Use Array.prototype.map()
and window.encodeURIComponent()
to encode each field's value. Use Array.prototype.join()
with appropriate argumens to produce an appropriate query string.
const serializeForm = form => Array.from(new FormData(form), field => field.map(encodeURIComponent).join('=')).join('&');
serializeForm(document.querySelector('#form')); // email=test%40email.com&name=Test%20Name
setStyle
Sets the value of a CSS rule for the specified element.
Use element.style
to set the value of the CSS rule for the specified element to val
.
const setStyle = (el, ruleName, val) => (el.style[ruleName] = val);
setStyle(document.querySelector('p'), 'font-size', '20px'); // The first <p> element on the page will have a font-size of 20px
show
Shows all the elements specified.
Use the spread operator (...
) and Array.prototype.forEach()
to clear the display
property for each element specified.
const show = (...el) => [...el].forEach(e => (e.style.display = ''));
show(...document.querySelectorAll('img')); // Shows all <img> elements on the page
smoothScroll
Smoothly scrolls the element on which it's called into the visible area of the browser window.
Use .scrollIntoView
method to scroll the element. Pass { behavior: 'smooth' }
to .scrollIntoView
so it scrolls smoothly.
const smoothScroll = element => document.querySelector(element).scrollIntoView({ behavior: 'smooth' });
smoothScroll('#fooBar'); // scrolls smoothly to the element with the id fooBar smoothScroll('.fooBar'); // scrolls smoothly to the first element with a class of fooBar
toggleClass
Toggle a class for an element.
Use element.classList.toggle()
to toggle the specified class for the element.
const toggleClass = (el, className) => el.classList.toggle(className);
toggleClass(document.querySelector('p.special'), 'special'); // The paragraph will not have the 'special' class anymore
triggerEvent
Triggers a specific event on a given element, optionally passing custom data.
Use new CustomEvent()
to create an event from the specified eventType
and details. Use el.dispatchEvent()
to trigger the newly created event on the given element. Omit the third argument, detail
, if you do not want to pass custom data to the triggered event.
const triggerEvent = (el, eventType, detail) => el.dispatchEvent(new CustomEvent(eventType, { detail }));
triggerEvent(document.getElementById('myId'), 'click'); triggerEvent(document.getElementById('myId'), 'click', { username: 'bob' });
UUIDGeneratorBrowser
Generates a UUID in a browser.
Use crypto
API to generate a UUID, compliant with RFC4122 version 4.
const UUIDGeneratorBrowser = () => ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c => (c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16) );
UUIDGeneratorBrowser(); // '7982fcfe-5721-4632-bede-6000885be57d'