rc-align模块基于dom-align模块实现,用于以参照节点调整事件源节点的位置。
Align.js
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _reactDom = require('react-dom'); var _reactDom2 = _interopRequireDefault(_reactDom); // domAlign(sourceNode,refNode,options)根据参照节点refNode和配置options调整事件源节点的位置 var _domAlign = require('dom-align'); var _domAlign2 = _interopRequireDefault(_domAlign); var _addEventListener = require('rc-util/lib/Dom/addEventListener'); var _addEventListener2 = _interopRequireDefault(_addEventListener); var _isWindow = require('./isWindow'); var _isWindow2 = _interopRequireDefault(_isWindow); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } // 设置定时器,执行fn;调用返回值启动执行定时器;调用返回值的clear方法,清除定时器 function buffer(fn, ms) { var timer = void 0; function clear() { if (timer) { clearTimeout(timer); timer = null; } } function bufferFn() { clear(); timer = setTimeout(fn, ms); } bufferFn.clear = clear; return bufferFn; } var Align = _react2["default"].createClass({ displayName: 'Align', propTypes: { childrenProps: _react.PropTypes.object,// 为props.children提供props的映射,属性值为this.props的属性名 align: _react.PropTypes.object.isRequired,// 位置调整配置项 target: _react.PropTypes.func,// 获取参照节点,同时用于判断组件重绘时是否调整事件源节点的位置 onAlign: _react.PropTypes.func,// 事件源节点位置调整后执行的回调函数 monitorBufferTime: _react.PropTypes.number,// 监听resize事件的定时事件 monitorWindowResize: _react.PropTypes.bool,// 浏览器缩放时是否调整事件源节点的位置 disabled: _react.PropTypes.bool,// 是否启用位置调整 children: _react.PropTypes.any// 实际显示用的待渲染节点 }, getDefaultProps: function getDefaultProps() { return { target: function target() { return window; }, onAlign: function onAlign(sourceNode,offsetInfo) {}, monitorBufferTime: 50, monitorWindowResize: false, disabled: false }; }, componentDidMount: function componentDidMount() { var props = this.props; // 调整事件源节点的位置 this.forceAlign(); // 监听窗口缩放resize事件 if (!props.disabled && props.monitorWindowResize) { this.startMonitorWindowResize(); } }, componentDidUpdate: function componentDidUpdate(prevProps) { var reAlign = false; var props = this.props; // props.align或props.target返回值变更,重新调整事件源节点的位置 if (!props.disabled) { if (prevProps.disabled || prevProps.align !== props.align) { reAlign = true; } else { var lastTarget = prevProps.target(); var currentTarget = props.target(); if ((0, _isWindow2["default"])(lastTarget) && (0, _isWindow2["default"])(currentTarget)) { reAlign = false; } else if (lastTarget !== currentTarget) { reAlign = true; } } } if (reAlign) { this.forceAlign(); } if (props.monitorWindowResize && !props.disabled) { this.startMonitorWindowResize(); } else { this.stopMonitorWindowResize(); } }, // 组件卸载时,解绑resize事件及清除定时器 componentWillUnmount: function componentWillUnmount() { this.stopMonitorWindowResize(); }, // 当浏览器窗口缩放、即'resize'事件发生时,调整事件源节点的位置 startMonitorWindowResize: function startMonitorWindowResize() { if (!this.resizeHandler) { this.bufferMonitor = buffer(this.forceAlign, this.props.monitorBufferTime); this.resizeHandler = (0, _addEventListener2["default"])(window, 'resize', this.bufferMonitor); } }, // 解绑'resize'事件的监听函数、及清除调整位置的定时器 stopMonitorWindowResize: function stopMonitorWindowResize() { if (this.resizeHandler) { this.bufferMonitor.clear(); this.resizeHandler.remove(); this.resizeHandler = null; } }, // 调用domAlign调整事件源节点的位置,并执行this.props.onAlign方法 forceAlign: function forceAlign() { var props = this.props; if (!props.disabled) { // 获取事件源节点,即this.props.children渲染后的节点元素 var source = _reactDom2["default"].findDOMNode(this); // 调用domAlign调整事件源节点的位置后,再调用this.props.onAlign(source,offsetInfo) // 参数offsetInfo是事件源节点实际位置调整数据 props.onAlign(source, (0, _domAlign2["default"])(source, props.target(), props.align)); } }, // rc-align只为props.children提供位置调整的实现,渲染依旧是props.children的固有机制,除去props变更 render: function render() { var _props = this.props, childrenProps = _props.childrenProps, children = _props.children; // this.props.children只允许为单个reactElement var child = _react2["default"].Children.only(children); // this.props.children映射this.props中将要置入子元素child的属性 if (childrenProps) { var newProps = {}; for (var prop in childrenProps) { if (childrenProps.hasOwnProperty(prop)) { newProps[prop] = this.props[childrenProps[prop]]; } } // 使用react.cloneElement方法为child提供新的props return _react2["default"].cloneElement(child, newProps); } return child; } }); exports["default"] = Align; module.exports = exports['default'];
dom-align:index.js
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _utils = require('./utils'); var _utils2 = _interopRequireDefault(_utils); var _getOffsetParent = require('./getOffsetParent'); var _getOffsetParent2 = _interopRequireDefault(_getOffsetParent); var _getVisibleRectForElement = require('./getVisibleRectForElement'); var _getVisibleRectForElement2 = _interopRequireDefault(_getVisibleRectForElement); var _adjustForViewport = require('./adjustForViewport'); var _adjustForViewport2 = _interopRequireDefault(_adjustForViewport); var _getRegion = require('./getRegion'); var _getRegion2 = _interopRequireDefault(_getRegion); var _getElFuturePos = require('./getElFuturePos'); var _getElFuturePos2 = _interopRequireDefault(_getElFuturePos); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } // http://yiminghe.iteye.com/blog/1124720 /** * align dom node flexibly * @author [email protected] */ function isFailX(elFuturePos, elRegion, visibleRect) { return elFuturePos.left < visibleRect.left || elFuturePos.left + elRegion.width > visibleRect.right; } function isFailY(elFuturePos, elRegion, visibleRect) { return elFuturePos.top < visibleRect.top || elFuturePos.top + elRegion.height > visibleRect.bottom; } function isCompleteFailX(elFuturePos, elRegion, visibleRect) { return elFuturePos.left > visibleRect.right || elFuturePos.left + elRegion.width < visibleRect.left; } function isCompleteFailY(elFuturePos, elRegion, visibleRect) { return elFuturePos.top > visibleRect.bottom || elFuturePos.top + elRegion.height < visibleRect.top; } function flip(points, reg, map) { var ret = []; _utils2["default"].each(points, function (p) { ret.push(p.replace(reg, function (m) { return map[m]; })); }); return ret; } function flipOffset(offset, index) { offset[index] = -offset[index]; return offset; } function convertOffset(str, offsetLen) { var n = void 0; if (/%$/.test(str)) { n = parseInt(str.substring(0, str.length - 1), 10) / 100 * offsetLen; } else { n = parseInt(str, 10); } return n || 0; } function normalizeOffset(offset, el) { offset[0] = convertOffset(offset[0], el.width); offset[1] = convertOffset(offset[1], el.height); } // 参数el待调整位置的事件源节点 // 参数refNode调整位置时的参照节点 // 参数align配置项{points,offset,targetOffset,overflow,target,source} // align.points设置参照节点和目标节点对齐时的参照点,如["tl","cr"],取el的左上位置对齐relNode的右中位置 // align.offset设置为百分比时,根据事件源节点的宽高执行偏移;数值时,即正向以该数值执行偏移[10,"20%"] // align.offset设置为百分比时,根据参照节点的宽高反向执行偏移;数值时,即反向执行偏移 // align.adjustX、adjustY可放置区域无法容纳事件源节点时,反向进行调整;若反向调整失败,削减事件源节点的宽高进行调整 // align.useCssRight、useCssBottom、align.useCssTransform设置调整事件源节点的模式 function domAlign(el, refNode, align) { var points = align.points; var offset = align.offset || [0, 0]; var targetOffset = align.targetOffset || [0, 0]; var overflow = align.overflow; var target = align.target || refNode; var source = align.source || el; offset = [].concat(offset); targetOffset = [].concat(targetOffset); overflow = overflow || {}; var newOverflowCfg = {}; var fail = 0; // 获取事件源节点position="static"的祖先节点,作为可放置范围,计算其top、left、bottom、right var visibleRect = (0, _getVisibleRectForElement2["default"])(source); // 事件源节点所占的区域, 含left、top、width、height属性 var elRegion = (0, _getRegion2["default"])(source); // 目标节点所占的区域, 含left、top、width、height属性 var refNodeRegion = (0, _getRegion2["default"])(target); // 将偏移量offset转换成数值 normalizeOffset(offset, elRegion); normalizeOffset(targetOffset, refNodeRegion); // 根据对齐点points及偏移量offset、targetOffset计算事件源节点最终的x、y坐标{left,top} var elFuturePos = (0, _getElFuturePos2["default"])(elRegion, refNodeRegion, points, offset, targetOffset); // 获取事件源节点对齐后的left、top属性,返回{left,top,width,height} var newElRegion = _utils2["default"].merge(elRegion, elFuturePos); // 可放置区域不能完全容纳事件源节点,作反向调整,如左对齐的变成右对齐、相应偏移量也反向 if (visibleRect && (overflow.adjustX || overflow.adjustY)) { if (overflow.adjustX) { if (isFailX(elFuturePos, elRegion, visibleRect)) { var newPoints = flip(points, /[lr]/ig, { l: 'r', r: 'l' }); var newOffset = flipOffset(offset, 0); var newTargetOffset = flipOffset(targetOffset, 0); var newElFuturePos = (0, _getElFuturePos2["default"])(elRegion, refNodeRegion, newPoints, newOffset, newTargetOffset); if (!isCompleteFailX(newElFuturePos, elRegion, visibleRect)) { fail = 1; points = newPoints; offset = newOffset; targetOffset = newTargetOffset; } } } if (overflow.adjustY) { if (isFailY(elFuturePos, elRegion, visibleRect)) { var _newPoints = flip(points, /[tb]/ig, { t: 'b', b: 't' }); var _newOffset = flipOffset(offset, 1); var _newTargetOffset = flipOffset(targetOffset, 1); var _newElFuturePos = (0, _getElFuturePos2["default"])(elRegion, refNodeRegion, _newPoints, _newOffset, _newTargetOffset); if (!isCompleteFailY(_newElFuturePos, elRegion, visibleRect)) { fail = 1; points = _newPoints; offset = _newOffset; targetOffset = _newTargetOffset; } } } if (fail) { elFuturePos = (0, _getElFuturePos2["default"])(elRegion, refNodeRegion, points, offset, targetOffset); _utils2["default"].mix(newElRegion, elFuturePos); } newOverflowCfg.adjustX = overflow.adjustX && isFailX(elFuturePos, elRegion, visibleRect); newOverflowCfg.adjustY = overflow.adjustY && isFailY(elFuturePos, elRegion, visibleRect); // 配置项要求调整,同时反向调整失败,削减事件源节点的宽高进行调整;newElRegion计算事件源节点允许的宽高 if (newOverflowCfg.adjustX || newOverflowCfg.adjustY) { newElRegion = (0, _adjustForViewport2["default"])(elFuturePos, elRegion, visibleRect, newOverflowCfg); } } // 削减宽高 if (newElRegion.width !== elRegion.width) { _utils2["default"].css(source, 'width', _utils2["default"].width(source) + newElRegion.width - elRegion.width); } if (newElRegion.height !== elRegion.height) { _utils2["default"].css(source, 'height', _utils2["default"].height(source) + newElRegion.height - elRegion.height); } // 调整事件源节点的位置,尾参option约定调整的方式 _utils2["default"].offset(source, { left: newElRegion.left, top: newElRegion.top }, { useCssRight: align.useCssRight, useCssBottom: align.useCssBottom, useCssTransform: align.useCssTransform }); // 返回事件源节点实际的调整数据 return { points: points, offset: offset, targetOffset: targetOffset, overflow: newOverflowCfg }; } domAlign.__getOffsetParent = _getOffsetParent2["default"]; domAlign.__getVisibleRectForElement = _getVisibleRectForElement2["default"]; exports["default"] = domAlign; module.exports = exports['default'];