├── .editorconfig ├── .eslintrc.js ├── .gitignore ├── README.md ├── addClass.js ├── addEventListener.js ├── appendVendorPrefix.js ├── builtinStyle.js ├── canUseDOM.js ├── classNames.js ├── getVendorPrefix.js ├── getVendorPropertyName.js ├── hasClass.js ├── inViewport.js ├── insertKeyframesRule.js ├── insertRule.js ├── onEndTransition.js ├── package.json ├── removeClass.js ├── removeEventListener.js ├── throttle.js └── transitionEvents.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | 10 | # Change these settings to your own preference 11 | indent_style = space 12 | indent_size = 2 13 | 14 | # We recommend you to keep these unchanged 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | commonjs: true, 5 | es6: true, 6 | node: true 7 | }, 8 | extends: [ 9 | 'standard' 10 | ], 11 | globals: { 12 | Atomics: 'readonly', 13 | SharedArrayBuffer: 'readonly' 14 | }, 15 | parserOptions: { 16 | ecmaVersion: 2018 17 | }, 18 | rules: { 19 | semi: ['error', 'always'] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # node.js 6 | # 7 | lib/ 8 | node_modules/ 9 | npm-debug.log 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DOM Kit 2 | > Toolkit for DOM 3 | 4 | ``` 5 | npm install domkit --save 6 | ``` 7 | 8 | ## insertKeyframesRule 9 | 10 | ```js 11 | var insertKeyframesRule = require('domkit/insertKeyframesRule'); 12 | var keyframes = { 13 | '0%': { 14 | transform: 'scale(1)' 15 | }, 16 | '50%': { 17 | transform: 'scale(0.5)', 18 | opacity: 0.7 19 | }, 20 | '100%': { 21 | transform: 'scale(1)', 22 | opacity: 1 23 | } 24 | }; 25 | 26 | var animationName = insertKeyframesRule(keyframes); 27 | ``` 28 | 29 | ## insertRule 30 | 31 | ```js 32 | var insertRule = require('domkit/insertRule'); 33 | var css = '.foo {}' 34 | insertRule(css); 35 | ``` 36 | 37 | ## appendVendorPrefix 38 | 39 | ```js 40 | var appendVendorPrefix = require('domkit/appendVendorPrefix'); 41 | var style = { 42 | transform: 'scaleX(1)' 43 | } 44 | appendVendorPrefix(style); 45 | ``` 46 | 47 | ## getVendorPrefix 48 | 49 | ```js 50 | var getVendorPrefix = require('domkit/getVendorPrefix'); 51 | var vendorPrefix = getVendorPrefix(); // => -webkit- 52 | ``` 53 | 54 | ## addClass 55 | 56 | ```js 57 | var addClass = require('domkit/addClass'); 58 | addClass(this.getDOMNode(), 'foo'); 59 | ``` 60 | 61 | ## removeClass 62 | 63 | ```js 64 | var removeClass = require('domkit/removeClass'); 65 | removeClass(this.getDOMNode(), 'foo'); 66 | ``` 67 | 68 | ## hasClass 69 | 70 | ```js 71 | var hasClass = require('domkit/hasClass'); 72 | hasClass(this.getDOMNode(), 'foo'); // => true 73 | ``` 74 | 75 | ## transitionEventsa 76 | 77 | ```js 78 | var transitionEvents = require('domkit/transitionEvents'); 79 | transitionEvents.addEndEventListener(node, eventListener); 80 | transitionEvents.removeEndEventListener(node, eventListener); 81 | ``` 82 | 83 | ## classNames 84 | 85 | ```js 86 | var classNames = require('domkit/classNames'); 87 | 88 | classNames('foo', 'bar'); // => 'foo bar' 89 | classNames('foo', { bar: true }); // => 'foo bar' 90 | classNames({ foo: true }, { bar: true }); // => 'foo bar' 91 | classNames({ foo: true, bar: true }); // => 'foo bar' 92 | 93 | // lots of arguments of various types 94 | classNames('foo', { bar: true, duck: false }, 'baz', { quux: true }) // => 'foo bar baz quux' 95 | 96 | // other falsy values are just ignored 97 | classNames(null, false, 'bar', undefined, 0, 1, { baz: null }, ''); // => 'bar 1' 98 | 99 | // Arrays will be recursively flattened as per the rules above: 100 | var arr = ['b', { c: true, d: false }]; 101 | classNames('a', arr); // => 'a b c' 102 | ``` 103 | 104 | ## canUseDOM 105 | 106 | ```js 107 | var canUseDOM = require('domkit/canUseDOM'); 108 | if(canUseDOM){ 109 | // balabala 110 | } 111 | ``` 112 | 113 | ## addEventListener 114 | 115 | ```js 116 | var addEventListener = require('domkit/addEventListener'); 117 | addEventListener(window, 'scroll', handle) 118 | ``` 119 | 120 | ## removeEventListener 121 | ```js 122 | var removeEventListener = require('domkit/removeEventListener'); 123 | removeEventListener(window, 'scroll', handle) 124 | ``` 125 | 126 | ## throttle 127 | ```js 128 | var throttle = require('domkit/throttle'); 129 | throttle(fn, 100) 130 | ``` 131 | 132 | ## onEndTransition 133 | ```js 134 | var onEndTransition = require('domkit/onEndTransition'); 135 | onEndTransition(el, handle) 136 | ``` 137 | 138 | ## inViewport 139 | ```js 140 | var inViewport = require('domkit/inViewport'); 141 | inViewport(el) // true if elem is in the current viewport 142 | inViewport(el, 100) // true if elem is in the current viewport or within 100px of it 143 | inViewport(el, -100) // true if elem is in the current viewport and not within 99px of the edge 144 | ``` 145 | 146 | ## Browser Support 147 | 148 | ![IE](https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png) | ![Chrome](https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png) | ![Firefox](https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png) | ![Opera](https://raw.githubusercontent.com/alrra/browser-logos/master/src/opera/opera_48x48.png) | ![Safari](https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png) 149 | --- | --- | --- | --- | --- | 150 | IE 6+ ✔ | Chrome 4.0+ ✔ | Firefox 16.0+ ✔ | Opera 15.0+ ✔ | Safari 4.0+ ✔ | 151 | -------------------------------------------------------------------------------- /addClass.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var hasClass = require('./hasClass'); 4 | 5 | module.exports = function (element, className) { 6 | if (element.classList) { 7 | element.classList.add(className); 8 | } else if (!hasClass(element, className)) { 9 | element.className = element.className + ' ' + className; 10 | } 11 | return element; 12 | }; 13 | -------------------------------------------------------------------------------- /addEventListener.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (elem, type, eventHandle) { 4 | if (elem == null) { 5 | return; 6 | } 7 | if (elem.addEventListener) { 8 | elem.addEventListener(type, eventHandle, false); 9 | } else if (elem.attachEvent) { 10 | elem.attachEvent('on' + type, eventHandle); 11 | } else { 12 | elem['on' + type] = eventHandle; 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /appendVendorPrefix.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var getVendorPropertyName = require('./getVendorPropertyName'); 4 | 5 | module.exports = function (target, sources) { 6 | var to = Object(target); 7 | var hasOwnProperty = Object.prototype.hasOwnProperty; 8 | 9 | for (var nextIndex = 1; nextIndex < arguments.length; nextIndex++) { 10 | var nextSource = arguments[nextIndex]; 11 | if (nextSource == null) { 12 | continue; 13 | } 14 | 15 | var from = Object(nextSource); 16 | 17 | for (var i in from) { 18 | if (hasOwnProperty.call(from, i)) { 19 | to[i] = from[i]; 20 | } 21 | } 22 | } 23 | 24 | var prefixed = {}; 25 | for (var k in to) { 26 | prefixed[getVendorPropertyName(k)] = to[k]; 27 | } 28 | 29 | return prefixed; 30 | }; 31 | -------------------------------------------------------------------------------- /builtinStyle.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var canUseDOM = require('./canUseDOM'); 4 | 5 | module.exports = canUseDOM && document.createElement('div').style; 6 | -------------------------------------------------------------------------------- /canUseDOM.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = !!(typeof window !== 'undefined' && window.document && window.document.createElement); 4 | -------------------------------------------------------------------------------- /classNames.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | if (!Array.isArray) { 4 | Array.isArray = function (arg) { 5 | return Object.prototype.toString.call(arg) === '[object Array]'; 6 | }; 7 | } 8 | 9 | module.exports = function classNames () { 10 | var classes = ''; 11 | 12 | for (var i = 0; i < arguments.length; i++) { 13 | var arg = arguments[i]; 14 | if (!arg) continue; 15 | 16 | var argType = typeof arg; 17 | 18 | if (argType === 'string' || argType === 'number') { 19 | classes += ' ' + arg; 20 | } else if (Array.isArray(arg)) { 21 | classes += ' ' + classNames.apply(null, arg); 22 | } else if (argType === 'object') { 23 | for (var key in arg) { 24 | if (Object.prototype.hasOwnProperty.call(arg, key) && arg[key]) { 25 | classes += ' ' + key; 26 | } 27 | } 28 | } 29 | } 30 | 31 | return classes.substr(1); 32 | }; 33 | -------------------------------------------------------------------------------- /getVendorPrefix.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var cssVendorPrefix; 4 | var canUseDOM = require('./canUseDOM'); 5 | 6 | module.exports = function () { 7 | if (!canUseDOM) return; 8 | 9 | if (cssVendorPrefix) return cssVendorPrefix; 10 | 11 | var styles = window.getComputedStyle(document.documentElement, ''); 12 | var pre = (Array.prototype.slice.call(styles).join('').match(/-(moz|webkit|ms)-/) || (styles.OLink === '' && ['', 'o']))[1]; 13 | 14 | cssVendorPrefix = '-' + pre + '-'; 15 | 16 | return cssVendorPrefix; 17 | }; 18 | -------------------------------------------------------------------------------- /getVendorPropertyName.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var builtinStyle = require('./builtinStyle'); 4 | var prefixes = ['Moz', 'Webkit', 'O', 'ms']; 5 | var domVendorPrefix; 6 | 7 | // Helper function to get the proper vendor property name. (transition => WebkitTransition) 8 | module.exports = function (prop, isSupportTest) { 9 | var vendorProp; 10 | if (prop in builtinStyle) return prop; 11 | 12 | var UpperProp = prop.charAt(0).toUpperCase() + prop.substr(1); 13 | 14 | if (domVendorPrefix) { 15 | vendorProp = domVendorPrefix + UpperProp; 16 | if (vendorProp in builtinStyle) { 17 | return vendorProp; 18 | } 19 | } else { 20 | for (var i = 0; i < prefixes.length; ++i) { 21 | vendorProp = prefixes[i] + UpperProp; 22 | if (vendorProp in builtinStyle) { 23 | domVendorPrefix = prefixes[i]; 24 | return vendorProp; 25 | } 26 | } 27 | } 28 | 29 | // if support test, not fallback to origin prop name 30 | if (!isSupportTest) { 31 | return prop; 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /hasClass.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (element, className) { 4 | if (element.classList) { 5 | return element.classList.contains(className); 6 | } else { 7 | return (' ' + element.className + ' ').indexOf(' ' + className + ' ') > -1; 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /inViewport.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * verge 1.9.1+201402130803 3 | * https://github.com/ryanve/verge 4 | * MIT License 2013 Ryan Van Etten 5 | */ 6 | 'use strict'; 7 | 8 | var win = typeof window !== 'undefined' && window; 9 | var doc = typeof document !== 'undefined' && document; 10 | var docElem = doc && doc.documentElement; 11 | 12 | function viewportW () { 13 | var a = docElem.clientWidth; var b = win.innerWidth; 14 | return a < b ? b : a; 15 | } 16 | 17 | function viewportH () { 18 | var a = docElem.clientHeight; var b = win.innerHeight; 19 | return a < b ? b : a; 20 | } 21 | 22 | /** 23 | * @param {{top:number, right:number, bottom:number, left:number}} coords 24 | * @param {number=} cushion adjustment 25 | * @return {Object} 26 | */ 27 | function calibrate (coords, cushion) { 28 | var o = {}; 29 | cushion = +cushion || 0; 30 | o.width = (o.right = coords.right + cushion) - (o.left = coords.left - cushion); 31 | o.height = (o.bottom = coords.bottom + cushion) - (o.top = coords.top - cushion); 32 | return o; 33 | } 34 | 35 | /** 36 | * Cross-browser element.getBoundingClientRect plus optional cushion. 37 | * Coords are relative to the top-left corner of the viewport. 38 | * @since 1.0.0 39 | * @param {Element|Object} el element or stack (uses first item) 40 | * @param {number=} cushion +/- pixel adjustment amount 41 | * @return {Object|boolean} 42 | */ 43 | function rectangle (el, cushion) { 44 | el = el && !el.nodeType ? el[0] : el; 45 | if (!el || el.nodeType !== 1) return false; 46 | return calibrate(el.getBoundingClientRect(), cushion); 47 | } 48 | 49 | module.exports = function (el, cushion) { 50 | // Equiv to `inX(el, cushion) && inY(el, cushion)` but just manually do both 51 | // to avoid calling rectangle() twice. It gzips just as small like this. 52 | var r = rectangle(el, cushion); 53 | return !!r && r.bottom >= 0 && r.right >= 0 && r.top <= viewportH() && r.left <= viewportW(); 54 | }; 55 | -------------------------------------------------------------------------------- /insertKeyframesRule.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var insertRule = require('./insertRule'); 4 | var vendorPrefix = require('./getVendorPrefix')(); 5 | var index = 0; 6 | 7 | module.exports = function (keyframes) { 8 | // random name 9 | var name = 'anim_' + (++index) + (+new Date()); 10 | // The @-ms-keyframes prefix does not exist and will break for IE9 11 | var vendorPrefixExceptMs = vendorPrefix === '-ms-' ? '' : vendorPrefix; 12 | var css = '@' + vendorPrefixExceptMs + 'keyframes ' + name + ' {'; 13 | 14 | for (var key in keyframes) { 15 | css += key + ' {'; 16 | 17 | for (var property in keyframes[key]) { 18 | var part = ':' + keyframes[key][property] + ';'; 19 | // We do vendor prefix for every property 20 | css += vendorPrefix + property + part; 21 | css += property + part; 22 | } 23 | 24 | css += '}'; 25 | } 26 | 27 | css += '}'; 28 | 29 | insertRule(css); 30 | 31 | return name; 32 | }; 33 | -------------------------------------------------------------------------------- /insertRule.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var extraSheet; 4 | var canUseDOM = require('./canUseDOM'); 5 | 6 | module.exports = function (css) { 7 | if (!canUseDOM) return; 8 | 9 | if (!extraSheet) { 10 | // First time, create an extra stylesheet for adding rules 11 | extraSheet = document.createElement('style'); 12 | document.getElementsByTagName('head')[0].appendChild(extraSheet); 13 | // Keep reference to actual StyleSheet object (`styleSheet` for IE < 9) 14 | extraSheet = extraSheet.sheet || extraSheet.styleSheet; 15 | } 16 | 17 | var index = (extraSheet.cssRules || extraSheet.rules).length; 18 | extraSheet.insertRule(css, index); 19 | 20 | return extraSheet; 21 | }; 22 | -------------------------------------------------------------------------------- /onEndTransition.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var getVendorPropertyName = require('./getVendorPropertyName'); 4 | // http://stackoverflow.com/questions/16439161/how-to-support-transitionend-without-browser-sniffing 5 | var transEndEventNames = { 6 | WebkitTransition: 'webkitTransitionEnd', 7 | MozTransition: 'transitionend', 8 | OTransition: 'oTransitionEnd', 9 | msTransition: 'MSTransitionEnd', 10 | transition: 'transitionend' 11 | }; 12 | 13 | var transEventName = getVendorPropertyName('transition', true); 14 | var transEndEventName = transEndEventNames[transEventName]; 15 | var supportTransitions = !!transEventName; 16 | 17 | module.exports = function (el, callback) { 18 | var onEndCallbackFn = function (ev) { 19 | if (supportTransitions) { 20 | if (ev.target !== this) return; 21 | this.removeEventListener(transEndEventName, onEndCallbackFn); 22 | } 23 | if (callback && typeof callback === 'function') { callback.call(this); } 24 | }; 25 | if (supportTransitions) { 26 | el.addEventListener(transEndEventName, onEndCallbackFn); 27 | } else { 28 | onEndCallbackFn(); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "domkit", 3 | "version": "0.0.3", 4 | "description": "Toolkit for DOM", 5 | "author": "Yuanyan Cao", 6 | "license": "MIT", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/yuanyan/domkit.git" 10 | }, 11 | "keywords": [ 12 | "dom", 13 | "domkit", 14 | "toolkit" 15 | ], 16 | "scripts": { 17 | "lint": "eslint --fix *.js" 18 | }, 19 | "devDependencies": { 20 | "eslint": "^6.3.0", 21 | "eslint-config-standard": "^14.1.0", 22 | "eslint-plugin-import": "^2.18.2", 23 | "eslint-plugin-node": "^9.2.0", 24 | "eslint-plugin-promise": "^4.2.1", 25 | "eslint-plugin-standard": "^4.0.1" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /removeClass.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var hasClass = require('./hasClass'); 3 | 4 | module.exports = function (element, className) { 5 | if (hasClass(className)) { 6 | if (element.classList) { 7 | element.classList.remove(className); 8 | } else { 9 | element.className = (' ' + element.className + ' ') 10 | .replace(' ' + className + ' ', ' ').trim(); 11 | } 12 | } 13 | return element; 14 | }; 15 | -------------------------------------------------------------------------------- /removeEventListener.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (elem, type, eventHandle) { 4 | if (elem == null || typeof elem === 'undefined') { 5 | return; 6 | } 7 | if (elem.removeEventListener) { 8 | elem.removeEventListener(type, eventHandle, false); 9 | } else if (elem.detachEvent) { 10 | elem.detachEvent('on' + type, eventHandle); 11 | } else { 12 | elem['on' + type] = null; 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /throttle.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (fn, delay) { 4 | var allowSample = true; 5 | return function (e) { 6 | if (allowSample) { 7 | allowSample = false; 8 | setTimeout(function () { allowSample = true; }, delay); 9 | fn(e); 10 | } 11 | }; 12 | }; 13 | -------------------------------------------------------------------------------- /transitionEvents.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * EVENT_NAME_MAP is used to determine which event fired when a 5 | * transition/animation ends, based on the style property used to 6 | * define that event. 7 | */ 8 | var EVENT_NAME_MAP = { 9 | transitionend: { 10 | transition: 'transitionend', 11 | WebkitTransition: 'webkitTransitionEnd', 12 | MozTransition: 'mozTransitionEnd', 13 | OTransition: 'oTransitionEnd', 14 | msTransition: 'MSTransitionEnd' 15 | }, 16 | 17 | animationend: { 18 | animation: 'animationend', 19 | WebkitAnimation: 'webkitAnimationEnd', 20 | MozAnimation: 'mozAnimationEnd', 21 | OAnimation: 'oAnimationEnd', 22 | msAnimation: 'MSAnimationEnd' 23 | } 24 | }; 25 | 26 | var endEvents = []; 27 | 28 | function detectEvents () { 29 | var testEl = document.createElement('div'); 30 | var style = testEl.style; 31 | 32 | // On some platforms, in particular some releases of Android 4.x, 33 | // the un-prefixed "animation" and "transition" properties are defined on the 34 | // style object but the events that fire will still be prefixed, so we need 35 | // to check if the un-prefixed events are useable, and if not remove them 36 | // from the map 37 | if (!('AnimationEvent' in window)) { 38 | delete EVENT_NAME_MAP.animationend.animation; 39 | } 40 | 41 | if (!('TransitionEvent' in window)) { 42 | delete EVENT_NAME_MAP.transitionend.transition; 43 | } 44 | 45 | for (var baseEventName in EVENT_NAME_MAP) { 46 | var baseEvents = EVENT_NAME_MAP[baseEventName]; 47 | for (var styleName in baseEvents) { 48 | if (styleName in style) { 49 | endEvents.push(baseEvents[styleName]); 50 | break; 51 | } 52 | } 53 | } 54 | } 55 | 56 | if (typeof window !== 'undefined') { 57 | detectEvents(); 58 | } 59 | 60 | // We use the raw {add|remove}EventListener() call because EventListener 61 | // does not know how to remove event listeners and we really should 62 | // clean up. Also, these events are not triggered in older browsers 63 | // so we should be A-OK here. 64 | 65 | function addEventListener (node, eventName, eventListener) { 66 | node.addEventListener(eventName, eventListener, false); 67 | } 68 | 69 | function removeEventListener (node, eventName, eventListener) { 70 | node.removeEventListener(eventName, eventListener, false); 71 | } 72 | 73 | module.exports = { 74 | addEndEventListener: function (node, eventListener) { 75 | if (endEvents.length === 0) { 76 | // If CSS transitions are not supported, trigger an "end animation" 77 | // event immediately. 78 | window.setTimeout(eventListener, 0); 79 | return; 80 | } 81 | endEvents.forEach(function (endEvent) { 82 | addEventListener(node, endEvent, eventListener); 83 | }); 84 | }, 85 | 86 | removeEndEventListener: function (node, eventListener) { 87 | if (endEvents.length === 0) { 88 | return; 89 | } 90 | endEvents.forEach(function (endEvent) { 91 | removeEventListener(node, endEvent, eventListener); 92 | }); 93 | } 94 | }; 95 | --------------------------------------------------------------------------------