directives utils
repeat-click.js
在 input-number
组件中有应用 v-repeat-click
,当长按加或减时候,会持续调用绑定的函数
MouseEvent.button
是只读属性,它返回一个值,代表用户按下并触发了事件的鼠标按键,0:主按键,通常指鼠标左键或默认值; 1:辅助按键,通常指鼠标滚轮中键; 2:次按键,通常指鼠标右键
mousewheel.js
normalize-wheelMouse wheel normalization across multiple multiple browsers. normalize-wheel
was used to standardize the mousewheel event
If you need to react to the mouse wheel in a predictable way, this code is like your bestest friend. As of today, there are 4 DOM event types you can listen to:
'wheel' -- Chrome(31+), FF(17+), IE(9+)
'mousewheel' -- Chrome, IE(6+), Opera, Safari
'MozMousePixelScroll' -- FF(3.5 only!) (2010-2013) -- don't bother!
'DOMMouseScroll' -- FF(0.9.7+) since 2003
In your event callback, use this code to get sane interpretation of the deltas. This code will return an object with 4 properties:
spinX -- normalized spin speed (use for zoom) - x plane
spinY -- " - y plane
pixelX -- normalized distance (to pixels) - x plane
pixelY -- " - y plane
fireFox 中监听的是 DOMMouseScroll
事件,其他浏览器中监听的是 mousewheel
事件, v-mousewheel在table组件中有所使用
import normalizeWheel from 'normalize-wheel';
const isFirefox = typeof navigator !== 'undefined' && navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
const mousewheel = function (element, callback) {
if (element && element.addEventListener) {
element.addEventListener(isFirefox ? 'DOMMouseScroll' : 'mousewheel', function (event) {
const normalized = normalizeWheel(event);
callback && callback.apply(this, [event, normalized]);
});
}
};
clickoutside.js
v-clickoutside
在element中应用很广泛,以 <el-color-picker />
组件为例,当组件挂载的时候,clickoutside.js
模块会将使用 v-clickoutside
指令的dom元素保存到nodeList
变量中,并且在该 dom
元素中绑定属性为 @@clickoutsideContext
的对象,该对象上绑定了3个重要的属性
documentHandler
: 当 documentmouseup
事件触发时,nodeList会循环触发所有绑定v-clickoutside
指令所在dom元素上的documentHandler
函数, 并且判断当前mouse事件是否作用于 dom 元素的内部还是外部,当作用于dom外部的时候,会执行v-clickoutside
绑定的函数,该函数执行的时候会传入两个事件对象mouseup
和mousedown
methodName
:v-clickoutside
指令绑定的函数名,可以通过vnode.context[el[ctx].methodName]()
调用bindingFn
:v-clickoutside
指令绑定的函数对象,可以通过el[ctx].bindingFn()
直接调用
当前dom元素包含或者就是点击的元素时候,判定位作用于当前dom上
el.contains(mouseup.target) || el.contains(mousedown.target) || el === mouseup.target
当前vue实例存在popperElm(弹出层) (vnode.context.popperElm && (vnode.context.popperElm.contains(mouseup.target) || vnode.context.popperElm.contains( mousedown.target)))
// clickoutside.js
import Vue from 'vue';
import {on} from 'element-ui/src/utils/dom';
const nodeList = [];
const ctx = '@@clickoutsideContext';
let startClick;
let seed = 0;
!Vue.prototype.$isServer && on(document, 'mousedown', e => (startClick = e));
!Vue.prototype.$isServer && on(document, 'mouseup', e => {
nodeList.forEach(node => node[ctx].documentHandler(e, startClick));
});
function createDocumentHandler(el, binding, vnode) {
return function (mouseup = {}, mousedown = {}) {
// vnode.context 是 Vue组件实例
if (!vnode ||
!vnode.context ||
!mouseup.target ||
!mousedown.target ||
el.contains(mouseup.target) ||
el.contains(mousedown.target) ||
el === mouseup.target ||
(vnode.context.popperElm &&
(vnode.context.popperElm.contains(mouseup.target) ||
vnode.context.popperElm.contains(mousedown.target)))) return;
if (binding.expression &&
el[ctx].methodName &&
vnode.context[el[ctx].methodName]) {
vnode.context[el[ctx].methodName]();
} else {
el[ctx].bindingFn && el[ctx].bindingFn();
}
};
}
/**
* v-clickoutside
* @desc 点击元素外面才会触发的事件
*/
export default {
bind(el, binding, vnode) {
nodeList.push(el);
const id = seed++;
el[ctx] = {
id,
documentHandler: createDocumentHandler(el, binding, vnode),
methodName: binding.expression,
bindingFn: binding.value
};
},
update(el, binding, vnode) {
el[ctx].documentHandler = createDocumentHandler(el, binding, vnode);
el[ctx].methodName = binding.expression;
el[ctx].bindingFn = binding.value;
},
unbind(el) {
let len = nodeList.length;
for (let i = 0; i < len; i++) {
if (nodeList[i][ctx].id === el[ctx].id) {
nodeList.splice(i, 1);
break;
}
}
delete el[ctx];
}
};
mixins
emitter.js
dispatch
方法:通过componentName
向上搜索 parent component,并触发父组件上的 eventbroadcast
方法:通过componentName
向下搜索 children component,并触发子组件上的 event
this.dispatch('ElFormItem', 'el.form.change', value);
this.broadcast('ElSelectDropdown', 'updatePopper');
transitions
collapse-transition.js
这是一个组件名为ElCollapseTransition
的函数式组件,它的 render
函数返回值得是一个 transition
组件,通过js操作子组件的 height
, 它会使得包裹其中的子组件呈现过渡展开的效果,在 Collapse 折叠面板
中有所使用
return h('transition', data, children)
<transition>
元素作为单个元素/组件的过渡效果。<transition>
只会把过渡效果应用到其包裹的内容上,而不会额外渲染 DOM 元素,也不会出现在可被检查的组件层级中
utils
after-leave.js
这是一个兼容性工具函数,有的浏览器当前组件上的 after-leave
事件不会触发,after-leave.js会在当vue组件前实例上再次绑定一个 after-leave
事件,绑定完成之后默认400ms之后会执行绑定的回调函数,在element loading组件中有所使用,loading组件最外层是一个 transition
组件,当过渡完成之后,transition组件会触发当前loading组件上的after-leave
事件,但是在某些浏览器下可能会失效,则400ms之后回调函数也会执行,如果两者都发生了调用,after-leave.js中的 called
标记会保证回调只执行一次
<!--loading组件中的 after-leave 事件的触发-->
<transition name="el-loading-fade" @after-leave="handleAfterLeave"></transition>
<script>
handleAfterLeave()
{
this.$emit('after-leave')
}
</script>
date.js && date-util.js
date.js is Modified from fecha which is a Lightweight date formatting and parsing utils(~2KB) and Meant to replace parsing and formatting functionality of moment.js
date.util.js is for date-picker component, which include some useful functions for date operation.
interesting Question: Incrementing a date in JavaScript
dom.js
- on
- off
- once
- hasClass
- addClass
- removeClass
- getStyle
- setStyle
- isScroll
- getScrollContainer
- isInContainer
utils/popup/popup-manager.js
&& utils/popup/index.js
popper.js && vue-popper.js && Popper.js V1
A popper is an element on the screen which "pops out" from the natural flow of your application. Common examples of poppers are tooltips, popovers, and drop-downs.
Popper.js is a positioning engine; its purpose is to calculate the position of an element to make it possible to position it near a given reference element.
vue-popper.js
vue integrate popper.js, export as vue mixins,used by ** color-picker,cascader,date-picker,dropdown,menu,popover,select,tooltip,table(filter-panel)** .etc positioning related components
utils/popup/index.js
index.js 中默认导出了 popup mixin(整合了PopupManager), 它的作用是用来管理弹出框用的,在drawer,dialog,message-box 组件中均有应用;
此外还导出了 PopupManager
对象,在 loading,message,notification,table(filter-panel) 组件中均有应用,用于管理弹出层
resize-event.js
使用了 resize-observer-polyfill
npm包来兼容 ResizeObserver
浏览器对象, 当被监听的 dom 元素 resize 事件触发的时候, 执行相应的 handler
// 使用
addResizeListener(this.$el, this.handleResize);
if (this.$el && this.handleResize) removeResizeListener(this.$el, this.handleResize);
scrollbar-width.js
由于各个浏览器的滚动条宽度不一致,这个函数是用来获取当前浏览器滚动条宽度的,通过在 body 上创建一个不可见 div 元素,通过设置样式使其出现滚动条,计算滚动条宽度后移除该 div 元素,这个主要用于 element-ui 内置组件 scrollbar 中,它是一个模拟了横竖滚动条的容器组件
scroll-into-view.js
在 select,date-picker(time-select) 中有应用,当 select 组件展开的时候,选中项应该展示在可视区域中
获取选中子元素相对于 container 的所有 offsetParents,处于可视区域上半部分的,当前选中元素的 selected.offsetTop 值必然小于 container.scrollTop( offsetTop相对于父元素的左上角计算);处于可视区域下半部分的, 当前选中元素的 selected.offsetTop + selected.offsetHeight 值必然大于 container.scrollTop + container.clientHeight
import Vue from 'vue';
export default function scrollIntoView(container, selected) {
if (Vue.prototype.$isServer) return;
if (!selected) {
container.scrollTop = 0;
return;
}
const offsetParents = [];
let pointer = selected.offsetParent;
// 获取 container 元素中某个子元素相对于 container 的所有 offsetParents
while (pointer && container !== pointer && container.contains(pointer)) {
offsetParents.push(pointer);
pointer = pointer.offsetParent;
}
// 累加当前元素距离container元素的 top 值
const top = selected.offsetTop + offsetParents.reduce((prev, curr) => (prev + curr.offsetTop), 0);
const bottom = top + selected.offsetHeight;
const viewRectTop = container.scrollTop;
const viewRectBottom = viewRectTop + container.clientHeight;
if (top < viewRectTop) { // seleted dom is hidden in upper scroll section
container.scrollTop = top;
} else if (bottom > viewRectBottom) { // seleted dom is hidden in bottom scroll section
container.scrollTop = bottom - container.clientHeight;
} else {
// do nothing cause seleted dom is in view
}
}