绩效考核手机版
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

209 lines
6.0 KiB

3 years ago
<template>
<view :id="$sUid" :class="custom_class" :style="wrap_style">
<view class="s-sticky" :class="c_class" :style="c_style">
<slot />
</view>
</view>
</template>
<script>
import componentMixin from '../../mixins/componentMixin';
/**
* s-sticky 粘性布局
* @description Sticky 组件与 CSS 中position: sticky属性实现的效果一致当组件在屏幕范围内时会按照正常的布局排列当组件滚出屏幕范围时始终会固定在屏幕顶部
* @property {Number} scrollTop 通过传入的scrollTop改变来触发scroll事件
* @property {String} position = [top|bottom] 吸顶方向
* @property {Number|String} offsetTop 吸顶时与顶部的距离单位rpx
* @property {Number|String} offsetBottom 吸底时与底部的距离单位rpx
* @property {Boolean} enable 启用粘性功能
* @property {Number|String} zIndex z-index在吸附时的z-index
* @property {Function} container 一个函数返回容器对应的 NodesRef 节点
* @event {Function} change (bool) 吸附状态改变事件
* @example <s-sticky></s-sticky>
*/
export default {
name: 'SSticky',
mixins: [componentMixin],
props: {
scrollTop: {
type: Number,
default: 0,
},
position: {
type: String,
default: 'top',
},
offsetTop: {
type: [Number, String],
default: 0,
},
offsetBottom: {
type: [Number, String],
default: 0,
},
enable: {
type: Boolean,
default: true,
},
zIndex: [Number, String],
container: Function,
},
data: () => ({
sticky: false,
fixed: false,
width: 0,
height: 0,
transform: 0,
windowHeight: 0,
windowTop: 0,
windowBottom: 0,
}),
computed: {
wrap_style() {
return this.enable && this.fixed ? this.$mergeStyle({
height: this.$addUnit(this.height, 'px'),
}) : '';
},
c_class() {
return this.$mergeClass({
's-sticky--fixed': this.fixed,
's-sticky--sticky': this.sticky,
}, this.custom_class);
},
c_style() {
const style = {};
if (this.enable) {
if (this.fixed) {
style[this.position] = this.$addUnit(this.offset + this.other_height, 'px');
style.width = this.$addUnit(this.width, 'px');
}
if (this.sticky) {
style.zIndex = this.zIndex;
}
if (this.transform) {
style.transform = 'translate3d(0, ' + this.transform + 'px, 0)';
}
}
return this.$mergeStyle(
style,
this.custom_style,
);
},
offset() {
return this.$toPx(this.position === 'top' ? this.offsetTop : this.offsetBottom);
},
other_height() {
return parseInt(this.position === 'top' ? this.windowTop : this.windowBottom);
},
scrollState() {
return [
this.enable,
this.scrollTop,
this.position,
this.container,
this.offsetTop,
this.offsetBottom,
];
},
},
watch: {
enable(bool) {
if (!bool) this.resetStickyData();
},
scrollState() {
this.onScroll();
},
sticky(bool) {
this.$emit('change', bool);
},
},
created() {
const systemInfo = uni.getSystemInfoSync();
this.windowHeight = systemInfo.windowHeight;
this.windowTop = systemInfo.windowTop || 0;
this.windowBottom = systemInfo.windowBottom || 0;
},
mounted() {
this.$nextTick(this.onScroll);
},
methods: {
getRootRect() {
return this.$getRect(`#${this.$sUid}`);
},
getContainerRect(container) {
return new Promise(resolve => container().boundingClientRect(resolve).exec());
},
clearTimer() {
if (this.timer) {
clearTimeout(this.timer);
this.timer = null;
}
},
onScroll() {
this.clearTimer();
this.timer = setTimeout(() => {
this.timer = null;
const { _isMounted, _isDestroyed, enable, position, offset, windowHeight } = this;
if (!_isMounted || _isDestroyed || !enable) return;
const container = this.$getPropsFn('container');
if (position === 'top') {
if (container) {
Promise.all([this.getRootRect(), this.getContainerRect(container)]).then(([rootRect, containerRect]) => {
this.resetStickyData();
if (offset + rootRect.height >= containerRect.height + containerRect.top) {
this.transform = containerRect.height - rootRect.height;
this.sticky = true;
} else if (offset >= rootRect.top && containerRect.bottom >= 0) {
this.setStickyData(rootRect);
}
});
} else {
this.getRootRect().then(rootRect => {
this.resetStickyData();
if (offset >= rootRect.top) {
this.setStickyData(rootRect);
}
});
}
} else {
if (container) {
Promise.all([this.getRootRect(), this.getContainerRect(container)]).then(([rootRect, containerRect]) => {
this.resetStickyData();
if (offset + rootRect.height >= windowHeight - containerRect.top) {
this.transform = rootRect.height - containerRect.height;
this.sticky = true;
} else if (offset >= windowHeight - rootRect.bottom && windowHeight >= containerRect.top) {
this.setStickyData(rootRect);
}
});
} else {
this.getRootRect().then(rootRect => {
this.resetStickyData();
if (offset >= windowHeight - rootRect.bottom) {
this.setStickyData(rootRect);
}
});
}
}
});
},
resetStickyData() {
this.sticky = false;
this.fixed = false;
this.width = 0;
this.height = 0;
this.transform = 0;
},
setStickyData(rect) {
this.sticky = true;
this.fixed = true;
this.width = rect.width;
this.height = rect.height;
},
},
};
</script>
<style lang="scss" src="./index.scss"></style>