refactor(Sticky): refactor with composition api

This commit is contained in:
chenjiahan 2020-08-28 11:47:05 +08:00
parent c9d47204db
commit 7fd4e3eabb

View File

@ -1,28 +1,18 @@
import { ref, reactive, computed, onMounted } from 'vue';
// Utils
import { isHidden } from '../utils/dom/style'; import { isHidden } from '../utils/dom/style';
import { unitToPx } from '../utils/format/unit'; import { unitToPx } from '../utils/format/unit';
import { createNamespace, isDef, inBrowser } from '../utils'; import { createNamespace } from '../utils';
import { getScrollTop, getElementTop, getScroller } from '../utils/dom/scroll'; import { getScrollTop, getElementTop, getScroller } from '../utils/dom/scroll';
import { BindEventMixin } from '../mixins/bind-event';
// Composition
import { useGlobalEvent } from '../composition/use-global-event';
import { useVisibilityChange } from '../composition/use-visibility-change';
const [createComponent, bem] = createNamespace('sticky'); const [createComponent, bem] = createNamespace('sticky');
export default createComponent({ export default createComponent({
mixins: [
BindEventMixin(function (bind, isBind) {
if (!this.scroller) {
this.scroller = getScroller(this.$el);
}
if (this.observer) {
const method = isBind ? 'observe' : 'unobserve';
this.observer[method](this.$el);
}
bind(this.scroller, 'scroll', this.onScroll, true);
this.onScroll();
}),
],
props: { props: {
zIndex: [Number, String], zIndex: [Number, String],
container: null, container: null,
@ -34,118 +24,102 @@ export default createComponent({
emits: ['scroll'], emits: ['scroll'],
data() { setup(props, { emit, slots }) {
return { const rootRef = ref();
const scrollerRef = ref();
const state = reactive({
fixed: false, fixed: false,
height: 0, height: 0,
transform: 0, transform: 0,
}; });
},
computed: { const offsetTop = computed(() => unitToPx(props.offsetTop));
offsetTopPx() {
return unitToPx(this.offsetTop);
},
style() { const style = computed(() => {
if (!this.fixed) { if (!state.fixed) {
return; return;
} }
const style = {}; const top = offsetTop.value ? `${offsetTop.value}px` : null;
const transform = state.transform
? `translate3d(0, ${state.transform}px, 0)`
: null;
if (isDef(this.zIndex)) { return {
style.zIndex = this.zIndex; top,
} zIndex: props.zIndex,
transform,
if (this.offsetTopPx && this.fixed) {
style.top = `${this.offsetTopPx}px`;
}
if (this.transform) {
style.transform = `translate3d(0, ${this.transform}px, 0)`;
}
return style;
},
},
created() {
// compatibility: https://caniuse.com/#feat=intersectionobserver
if (inBrowser && window.IntersectionObserver) {
this.observer = new IntersectionObserver(
(entries) => {
// trigger scroll when visibility changed
if (entries[0].intersectionRatio > 0) {
this.onScroll();
}
},
{ root: document.body }
);
}
},
methods: {
onScroll() {
if (isHidden(this.$el)) {
return;
}
this.height = this.$el.offsetHeight;
const { container, offsetTopPx } = this;
const scrollTop = getScrollTop(window);
const topToPageTop = getElementTop(this.$el);
const emitScrollEvent = () => {
this.$emit('scroll', {
scrollTop,
isFixed: this.fixed,
});
}; };
});
const emitScrollEvent = (scrollTop) => {
emit('scroll', {
scrollTop,
isFixed: state.fixed,
});
};
const onScroll = () => {
if (isHidden(rootRef.value)) {
return;
}
state.height = rootRef.value.offsetHeight;
const { container } = props;
const scrollTop = getScrollTop(window);
const topToPageTop = getElementTop(rootRef.value);
// The sticky component should be kept inside the container element // The sticky component should be kept inside the container element
if (container) { if (container) {
const bottomToPageTop = topToPageTop + container.offsetHeight; const bottomToPageTop = topToPageTop + container.offsetHeight;
if (scrollTop + offsetTopPx + this.height > bottomToPageTop) { if (scrollTop + offsetTop.value + state.height > bottomToPageTop) {
const distanceToBottom = this.height + scrollTop - bottomToPageTop; const distanceToBottom = state.height + scrollTop - bottomToPageTop;
if (distanceToBottom < this.height) { if (distanceToBottom < state.height) {
this.fixed = true; state.fixed = true;
this.transform = -(distanceToBottom + offsetTopPx); state.transform = -(distanceToBottom + offsetTop.value);
} else { } else {
this.fixed = false; state.fixed = false;
} }
emitScrollEvent(); emitScrollEvent(scrollTop);
return; return;
} }
} }
if (scrollTop + offsetTopPx > topToPageTop) { if (scrollTop + offsetTop.value > topToPageTop) {
this.fixed = true; state.fixed = true;
this.transform = 0; state.transform = 0;
} else { } else {
this.fixed = false; state.fixed = false;
} }
emitScrollEvent(); emitScrollEvent(scrollTop);
},
},
render() {
const { fixed } = this;
const style = {
height: fixed ? `${this.height}px` : null,
}; };
return ( onMounted(() => {
<div style={style}> scrollerRef.value = getScroller(rootRef.value);
<div class={bem({ fixed })} style={this.style}> });
{this.$slots.default?.()}
useGlobalEvent(scrollerRef, 'scroll', onScroll);
useVisibilityChange(rootRef, onScroll);
return () => {
const { fixed, height } = state;
const rootStyle = {
height: fixed ? `${height}px` : null,
};
return (
<div ref={rootRef} style={rootStyle}>
<div class={bem({ fixed })} style={style.value}>
{slots.default?.()}
</div>
</div> </div>
</div> );
); };
}, },
}); });