Skip to content

Commit

Permalink
Refactor/affix (#846)
Browse files Browse the repository at this point in the history
* refactor: top 定位方式优化

* test: 快照更新

* refactor(affix): 优化占位节点的生成方式

Co-authored-by: ontheroad1992 <[email protected]>
  • Loading branch information
ontheroad1992 and ontheroad1992 authored May 16, 2022
1 parent a0089d1 commit c2e3c55
Show file tree
Hide file tree
Showing 6 changed files with 208 additions and 170 deletions.
27 changes: 17 additions & 10 deletions examples/affix/demos/container.vue
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
<template>
<div class="affix-container">
<div
class="affix-container-demo1"
ref="affixContainer"
>
<div class="affix-container-demo1" ref="affixContainer">
<div class="background">
<t-affix
ref="affix"
:z-index="5"
:offset-top="50"
:offset-bottom="50"
:container="getContainer"
@fixedChange="handleFixedChange"
>
<t-button>FixedTop top:{{ fixedTop }}</t-button>
<t-button>Fixed open:{{ open }}</t-button>
</t-affix>
</div>
</div>
Expand All @@ -23,16 +21,26 @@
export default {
data() {
return {
fixedTop: 0,
open: 0,
fixedBottom: 0,
};
},
mounted() {
// 相对 window 的移动,使用会影响性能
this.$nextTick(() => {
window.addEventListener('scroll', this.$refs?.affix.handleScroll);
});
},
beforeDestroy() {
window.removeEventListener('scroll', this.$refs?.affix.handleScroll);
},
methods: {
getContainer() {
return this.$refs?.affixContainer;
},
handleFixedChange(affixed, { top }) {
this.fixedTop = top;
console.log('top', top);
this.open = affixed;
},
},
};
Expand All @@ -50,9 +58,8 @@ export default {
.background {
height: 1500px;
padding-top: 700px;
background:
-webkit-linear-gradient(top, transparent 19px, #E7E7E7 20px),
-webkit-linear-gradient(left, transparent 19px, #E7E7E7 20px);
background: -webkit-linear-gradient(top, transparent 19px, #e7e7e7 20px),
-webkit-linear-gradient(left, transparent 19px, #e7e7e7 20px);
background-size: 20px 20px;
}
}
Expand Down
141 changes: 78 additions & 63 deletions src/affix/affix.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,104 +9,119 @@ const name = `${prefix}-affix`;
export interface Affix extends Vue {
scrollContainer: ScrollContainerElement;
ticking: boolean;
containerHeight: number;
placeholderEL: HTMLElement;
$refs: {
affixWrapRef: HTMLElement;
affixRef: HTMLElement;
};
}

export default (Vue as VueConstructor<Affix>).extend({
name: 'TAffix',
props: {
...affixProps,
},
data() {
return {
fixedTop: false as false | number,
oldWidthHeight: { width: '0px', height: '0px' },
};
},
watch: {
offsetTop() {
this.calcInitValue();
this.handleScroll();
},
offsetBottom() {
this.calcInitValue();
this.handleScroll();
},
fixedTop(val) {
this.$emit('fixedChange', val !== false, { top: val });
if (isFunction(this.onFixedChange)) this.onFixedChange(val !== false, { top: Number(val) });
zIndex() {
this.handleScroll();
},
},
methods: {
handleScroll() {
const { scrollContainer, offsetTop, offsetBottom } = this;
const { affixWrapRef, affixRef } = this.$refs;
if (!this.ticking) {
window.requestAnimationFrame(() => {
const { top } = this.$el.getBoundingClientRect(); // top = 节点到页面顶部的距离,包含 scroll 中的高度
// top = 节点到页面顶部的距离,包含 scroll 中的高度
const {
top: wrapToTop,
width: wrapWidth,
height: wrapHeight,
} = affixWrapRef.getBoundingClientRect() ?? { top: 0, width: 0, height: 0 };

let containerTop = 0; // containerTop = 容器到页面顶部的距离
if (this.scrollContainer instanceof HTMLElement) {
containerTop = this.scrollContainer.getBoundingClientRect().top;
if (scrollContainer instanceof HTMLElement) {
containerTop = scrollContainer.getBoundingClientRect().top;
}
const calcTop = top - containerTop; // 节点顶部到 container 顶部的距离
const calcBottom = containerTop + this.containerHeight - this.offsetBottom; // 计算 bottom 相对应的 top 值
if (this.offsetTop !== undefined && calcTop <= this.offsetTop) {

let fixedTop: number | false;
const calcTop = wrapToTop - containerTop; // 节点顶部到 container 顶部的距离

const containerHeight = scrollContainer[scrollContainer instanceof Window ? 'innerHeight' : 'clientHeight'] - wrapHeight;
const calcBottom = containerTop + containerHeight - offsetBottom; // 计算 bottom 相对应的 top 值

if (offsetTop !== undefined && calcTop <= offsetTop) {
// top 的触发
this.fixedTop = containerTop + this.offsetTop;
} else if (this.offsetBottom !== undefined && top >= calcBottom) {
fixedTop = containerTop + offsetTop;
} else if (offsetBottom !== undefined && wrapToTop >= calcBottom) {
// bottom 的触发
this.fixedTop = calcBottom;
fixedTop = calcBottom;
} else {
this.fixedTop = false;
fixedTop = false;
}

if (affixRef) {
const affixed = fixedTop !== false;
const placeholderStatus = affixWrapRef.contains(this.placeholderEL);

if (affixed) {
affixRef.className = name;
affixRef.style.top = `${fixedTop}px`;
affixRef.style.width = `${wrapWidth}px`;
affixRef.style.height = `${wrapHeight}px`;

if (this.zIndex) {
affixRef.style.zIndex = `${this.zIndex}`;
}

// 插入占位节点
if (!placeholderStatus) {
this.placeholderEL.style.width = `${wrapWidth}px`;
this.placeholderEL.style.height = `${wrapHeight}px`;
affixWrapRef.appendChild(this.placeholderEL);
}
} else {
affixRef.removeAttribute('class');
affixRef.removeAttribute('style');

// 删除占位节点
placeholderStatus && this.placeholderEL.remove();
}

this.$emit('fixedChange', affixed, { top: fixedTop });
if (isFunction(this.onFixedChange)) this.onFixedChange(affixed, { top: Number(fixedTop) });
}

this.ticking = false;
});
this.ticking = true;
}
},
calcInitValue() {
const { scrollContainer } = this;
// 获取当前可视的高度
const containerHeight = scrollContainer[scrollContainer instanceof Window ? 'innerHeight' : 'clientHeight'];
// 需要减掉当前节点的高度,对比的高度应该从 border-top 比对开始
this.containerHeight = containerHeight - this.$el.clientHeight;
// 被包裹的子节点宽高
const { clientWidth, clientHeight } = this.$el.querySelector(`.${name}`) || this.$el;
this.oldWidthHeight = { width: `${clientWidth}px`, height: `${clientHeight}px` };

this.handleScroll();
},
},
async mounted() {
await this.$nextTick();
this.scrollContainer = getScrollContainer(this.container);
this.calcInitValue();
on(this.scrollContainer, 'scroll', this.handleScroll);
on(window, 'resize', this.calcInitValue);
if (!(this.scrollContainer instanceof Window)) on(window, 'scroll', this.handleScroll);
mounted() {
this.placeholderEL = document.createElement('div');
this.$nextTick(() => {
this.scrollContainer = getScrollContainer(this.container);
on(this.scrollContainer, 'scroll', this.handleScroll);
on(window, 'resize', this.handleScroll);
});
},
destroyed() {
if (!this.scrollContainer) return;
off(this.scrollContainer, 'scroll', this.handleScroll);
off(window, 'resize', this.calcInitValue);
if (!(this.scrollContainer instanceof Window)) off(window, 'scroll', this.handleScroll);
off(window, 'resize', this.handleScroll);
},
render() {
const {
$slots: { default: children },
oldWidthHeight,
fixedTop,
zIndex,
} = this;

// false 0 -1 1 都用实际的意义
if (fixedTop !== false) {
return (
<div>
<div style={oldWidthHeight}></div>
<div class={name} style={{ zIndex, top: `${fixedTop}px`, width: oldWidthHeight.width }}>
{children}
</div>
</div>
);
}

return <div>{children}</div>;
return (
<div ref="affixWrapRef">
<div ref="affixRef">{this.$slots.default}</div>
</div>
);
},
});
32 changes: 20 additions & 12 deletions test/ssr/__snapshots__/ssr.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@

exports[`ssr snapshot test renders ./examples/affix/demos/base.vue correctly 1`] = `
<div class="affix-base">
<div><button type="button" class="t-button t-size-m t-button--variant-base t-button--theme-primary"><span class="t-button__text">固钉</span></button></div>
<div>
<div><button type="button" class="t-button t-size-m t-button--variant-base t-button--theme-primary"><span class="t-button__text">固钉</span></button></div>
</div>
</div>
`;

exports[`ssr snapshot test renders ./examples/affix/demos/container.vue correctly 1`] = `
<div class="affix-container">
<div class="affix-container-demo1">
<div class="background">
<div><button type="button" class="t-button t-size-m t-button--variant-base t-button--theme-primary"><span class="t-button__text">FixedTop top:0</span></button></div>
<div>
<div><button type="button" class="t-button t-size-m t-button--variant-base t-button--theme-primary"><span class="t-button__text">Fixed open:0</span></button></div>
</div>
</div>
</div>
</div>
Expand Down Expand Up @@ -240,17 +244,19 @@ exports[`ssr snapshot test renders ./examples/anchor/demos/base.vue correctly 1`
<div style="display:flex;">
<div style="flex:1;"></div>
<div>
<div class="t-anchor t-size-m">
<div class="t-anchor__line">
<div class="t-anchor__line-cursor-wrapper">
<div class="t-anchor__line-cursor"></div>
<div>
<div class="t-anchor t-size-m">
<div class="t-anchor__line">
<div class="t-anchor__line-cursor-wrapper">
<div class="t-anchor__line-cursor"></div>
</div>
</div>
<div class="t-anchor__item"><a href="#基础锚点" title="基础锚点" target="_self" class="t-anchor__item-link">基础锚点</a></div>
<div class="t-anchor__item"><a href="#多级锚点" title="多级锚点" target="_self" class="t-anchor__item-link">多级锚点</a></div>
<div class="t-anchor__item"><a href="#指定容器锚点" title="指定容器锚点" target="_self" class="t-anchor__item-link">指定容器锚点</a></div>
<div class="t-anchor__item"><a href="#特定交互锚点" title="特定交互锚点" target="_self" class="t-anchor__item-link">特定交互锚点</a></div>
<div class="t-anchor__item"><a href="#不同尺寸的锚点" title="不同尺寸的锚点" target="_self" class="t-anchor__item-link">不同尺寸的锚点</a></div>
</div>
<div class="t-anchor__item"><a href="#基础锚点" title="基础锚点" target="_self" class="t-anchor__item-link">基础锚点</a></div>
<div class="t-anchor__item"><a href="#多级锚点" title="多级锚点" target="_self" class="t-anchor__item-link">多级锚点</a></div>
<div class="t-anchor__item"><a href="#指定容器锚点" title="指定容器锚点" target="_self" class="t-anchor__item-link">指定容器锚点</a></div>
<div class="t-anchor__item"><a href="#特定交互锚点" title="特定交互锚点" target="_self" class="t-anchor__item-link">特定交互锚点</a></div>
<div class="t-anchor__item"><a href="#不同尺寸的锚点" title="不同尺寸的锚点" target="_self" class="t-anchor__item-link">不同尺寸的锚点</a></div>
</div>
</div>
</div>
Expand Down Expand Up @@ -12017,7 +12023,9 @@ exports[`ssr snapshot test renders ./examples/table/demos/affix.vue correctly 1`
<div class="tdesign-demo-block-column-large tdesign-demo__table-affix" style="width:100%;">
<div><label class="t-checkbox t-is-checked"><input type="checkbox" checked="checked" class="t-checkbox__former"><span class="t-checkbox__input"></span><span class="t-checkbox__label">表头吸顶</span></label> <label class="t-checkbox t-is-checked" style="margin-left:32px;"><input type="checkbox" checked="checked" class="t-checkbox__former"><span class="t-checkbox__input"></span><span class="t-checkbox__label">表尾吸底</span></label> <label class="t-checkbox t-is-checked" style="margin-left:32px;"><input type="checkbox" checked="checked" class="t-checkbox__former"><span class="t-checkbox__input"></span><span class="t-checkbox__label">固定左侧列</span></label> <label class="t-checkbox t-is-checked" style="margin-left:32px;"><input type="checkbox" checked="checked" class="t-checkbox__former"><span class="t-checkbox__input"></span><span class="t-checkbox__label">固定右侧列</span></label></div>
<div class="t-table t-table--bordered t-table--affixed-header t-table--column-fixed" style="position:relative;">
<div offsetTop="0"></div>
<div offsetTop="0">
<div></div>
</div>
<div class="t-table__content">
<div class="t-table__resize-line" style="display:none;left:10px;height:10px;"></div>
<table class="t-table--layout-auto" style="width:;">
Expand Down
40 changes: 22 additions & 18 deletions test/unit/affix/__snapshots__/demo.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,18 @@ exports[`Affix Affix baseVue demo works fine 1`] = `
class="affix-base"
>
<div>
<button
class="t-button t-size-m t-button--variant-base t-button--theme-primary"
type="button"
>
<span
class="t-button__text"
<div>
<button
class="t-button t-size-m t-button--variant-base t-button--theme-primary"
type="button"
>
固钉
</span>
</button>
<span
class="t-button__text"
>
固钉
</span>
</button>
</div>
</div>
</div>
`;
Expand All @@ -30,16 +32,18 @@ exports[`Affix Affix containerVue demo works fine 1`] = `
class="background"
>
<div>
<button
class="t-button t-size-m t-button--variant-base t-button--theme-primary"
type="button"
>
<span
class="t-button__text"
<div>
<button
class="t-button t-size-m t-button--variant-base t-button--theme-primary"
type="button"
>
FixedTop top:0
</span>
</button>
<span
class="t-button__text"
>
Fixed open:0
</span>
</button>
</div>
</div>
</div>
</div>
Expand Down
Loading

0 comments on commit c2e3c55

Please sign in to comment.