Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Affix: Add component Affix #21192

Open
wants to merge 3 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions components.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
"page-header": "./packages/page-header/index.js",
"cascader-panel": "./packages/cascader-panel/index.js",
"avatar": "./packages/avatar/index.js",
"affix": "./packages/affix/index.js",
"drawer": "./packages/drawer/index.js",
"popconfirm": "./packages/popconfirm/index.js",
"skeleton": "./packages/skeleton/index.js",
Expand Down
1 change: 1 addition & 0 deletions examples/docs/en-US/affix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
## Affix
1 change: 1 addition & 0 deletions examples/docs/es/affix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
## Affix
1 change: 1 addition & 0 deletions examples/docs/fr-FR/affix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
## Affix
89 changes: 89 additions & 0 deletions examples/docs/zh-CN/affix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
## Affix 图钉

使用图钉可以将元素钉在某个位置,使其不随页面的滚动而滚动。常用于侧边栏菜单或者顶部菜单。

### 基础用法

基础的用法

:::demo
```html
<template>
<el-affix :target="getTarget">
<el-button type="primary">固定在顶部</el-button>
</el-affix>
</template>
<script>
export default {
methods: {
getTarget() {
return window.document.getElementsByClassName('page-component__scroll el-scrollbar')[0].children[0];
}
}
}
</script>
```
:::

### 设置偏移量

设置距离顶部达到指定距离时触发

:::demo
```html
<template>
<el-affix :offset-top="100" :target="getTarget">
<el-button type="primary">固定距离顶部100px的位置</el-button>
</el-affix>
</template>
<script>
export default {
methods: {
getTarget() {
return window.document.getElementsByClassName('page-component__scroll el-scrollbar')[0].children[0];
}
}
}
</script>
```
:::

### 底部固定

设置距离底部达到指定距离时触发

:::demo
```html
<template>
<el-affix :offset-bottom="100" :target="getTarget">
<el-button type="primary">固定距离底部100px的位置</el-button>
</el-affix>
</template>
<script>
export default {
methods: {
getTarget() {
return window.document.getElementsByClassName('page-component__scroll el-scrollbar')[0].children[0];
}
}
}
</script>
```
:::


### Attributes

| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|---------------|----------------|-----------------|--------|----------------|
| offset-top | 距离窗口顶部达到指定偏移量后触发 | Number | — | 0 |
| offset-bottom | 距离窗口底部达到指定偏移量后触发 | Number | — | - |
| target | 设置 Affix 相对于哪个元素进行固定,并监听其滚动事件,值为一个返回对应 DOM 元素的函数 | Function | — | () => window |
| scrolltargets | 监听其他元素的滚动事件,如果此元素的滚动会影响被固定元素的位置,值为一个返回对应 DOM 元素数组的函数 | Function | — | () => [window] |
| use-capture | addEventListener 原生的 useCapture 选项 | Boolean | — | false |


### Events
| 事件名称 | 说明 | 回调参数 |
|----------|--------|----------|
| change | 在固定状态发生改变时触发 | 固定则为true,否则为false |
16 changes: 16 additions & 0 deletions examples/nav.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,10 @@
{
"path": "/drawer",
"title": "Drawer 抽屉"
},
{
"path": "/affix",
"title": "Affix 图钉"
}
]
}
Expand Down Expand Up @@ -621,6 +625,10 @@
{
"path": "/drawer",
"title": "Drawer"
},
{
"path": "/affix",
"title": "Affix"
}
]
}
Expand Down Expand Up @@ -935,6 +943,10 @@
{
"path": "/drawer",
"title": "Drawer"
},
{
"path": "/affix",
"title": "Affix"
}
]
}
Expand Down Expand Up @@ -1249,6 +1261,10 @@
{
"path": "/drawer",
"title": "Drawer"
},
{
"path": "/affix",
"title": "Affix"
}
]
}
Expand Down
8 changes: 8 additions & 0 deletions packages/affix/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import Affix from './src/main';

/* istanbul ignore next */
Affix.install = function(Vue) {
Vue.component(Affix.name, Affix);
};

export default Affix;
155 changes: 155 additions & 0 deletions packages/affix/src/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import { on, off } from 'element-ui/src/utils/dom';

export default {
name: 'ElAffix',
props: {
offsetTop: {
type: Number
},
offsetBottom: {
type: Number
},
useCapture: {
type: Boolean,
default: false
},
target: {
type: Function,
default: () => window
},
scrolltargets: {
type: Function,
default: () => null
}
},
data() {
return {
styles: {},
affix: false,
slot: false,
slotStyle: {}
};
},
computed: {
el() {
return this.target();
},
els() {
return this.scrolltargets();
}
},
render(h) {

return h('div', [
h('div', { class: this.affix ? 'el-affix' : '', style: this.styles }, [this.$slots.default]),
this.affix ? h('div', { style: this.slotStyle }) : ''
]);
},
mounted() {
on(window, 'resize', this.handleScroll, this.useCapture);
on(this.el, 'scroll', this.handleScroll, this.useCapture);

if (Array.isArray(this.els)) {
for (let i = 0; i < this.els.length; i++) {
on(this.els[i], 'scroll', this.handleScroll, this.useCapture);
}
}

this.$nextTick(() => {
this.handleScroll();
});
},
beforeDestroy() {
off(window, 'resize', this.handleScroll, this.useCapture);
off(this.el, 'scroll', this.handleScroll, this.useCapture);
if (Array.isArray(this.els)) {
for (let i = 0; i < this.els.length; i++) {
off(this.els[i], 'scroll', this.handleScroll, this.useCapture);
}
}
},
methods: {
handleScroll() {
if (!this.el || !this.$el) {
return;
}
const offsetTop = this.getOffsetTop();
const offsetBottom = this.getOffsetBottom();

const rect = this.$el.getBoundingClientRect();
const targetRect = this.getTargetRect(this.el);

const fixedTop = this.getFixedTop(rect, targetRect, offsetTop);
const fixedBottom = this.getFixedBottom(rect, targetRect, offsetBottom);

if (fixedTop === undefined && fixedBottom === undefined) {
if (this.affix) {
this.slot = false;
this.slotStyle = {};
this.affix = false;
this.styles = null;

this.$emit('change', false);
}
return;
}
if (fixedTop !== undefined) {
// Fixed Top
if ((this.affix && this.styles.top !== `${fixedTop}px`) || !this.affix) {
this.slotStyle = {
width: rect.width + 'px',
height: rect.height + 'px'
};
this.styles = {
position: 'fixed',
top: `${fixedTop}px`,
width: rect.width + 'px',
height: rect.height + 'px'
};
}
} else if (fixedBottom !== undefined) {
// Fixed Bottom
if ((this.affix && this.styles.bottom !== `${fixedBottom}px`) || !this.affix) {
this.slotStyle = {
width: rect.width + 'px',
height: rect.height + 'px'
};
this.styles = {
position: 'fixed',
bottom: `${fixedBottom}px`,
width: rect.width + 'px',
height: rect.height + 'px'
};
}
}
if (!this.affix) {
this.slot = true;
this.affix = true;
this.$emit('change', true);
}
},
getTargetRect(target) {
return target !== window ? target.getBoundingClientRect() : ({ top: 0, bottom: target.innerHeight, left: 0 });
},
getOffsetTop() {
return this.offsetBottom === undefined && this.offsetTop === undefined ? 0 : this.offsetTop;
},
getOffsetBottom() {
return this.offsetBottom;
},
getFixedTop(rect, targetRect, offsetTop) {
if (offsetTop !== undefined && targetRect.top > rect.top - offsetTop) {
return offsetTop + targetRect.top;
}
return undefined;
},
getFixedBottom(rect, targetRect, offsetBottom) {
if (offsetBottom !== undefined && targetRect.bottom < rect.bottom + offsetBottom) {
const targetBottomOffset = window.innerHeight - targetRect.bottom;
return offsetBottom + targetBottomOffset;
}
return undefined;
}
}
};

6 changes: 6 additions & 0 deletions packages/theme-chalk/src/affix.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@import "mixins/mixins";
@import "common/var";

@include b(affix) {
z-index: $--index-affix;
}
1 change: 1 addition & 0 deletions packages/theme-chalk/src/common/var.scss
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ $--size-base: 14px !default;
$--index-normal: 1 !default;
$--index-top: 1000 !default;
$--index-popper: 2000 !default;
$--index-affix: 500 !default;

/* Disable base
-------------------------- */
Expand Down
1 change: 1 addition & 0 deletions packages/theme-chalk/src/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
@import "./avatar.scss";
@import "./drawer.scss";
@import "./popconfirm.scss";
@import "./affix.scss";
@import "./skeleton.scss";
@import "./skeleton-item.scss";
@import "./empty.scss";
Expand Down
3 changes: 3 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ import CascaderPanel from '../packages/cascader-panel/index.js';
import Avatar from '../packages/avatar/index.js';
import Drawer from '../packages/drawer/index.js';
import Popconfirm from '../packages/popconfirm/index.js';
import Affix from '../packages/affix/index.js';
import Skeleton from '../packages/skeleton/index.js';
import SkeletonItem from '../packages/skeleton-item/index.js';
import Empty from '../packages/empty/index.js';
Expand Down Expand Up @@ -169,6 +170,7 @@ const components = [
Avatar,
Drawer,
Popconfirm,
Affix,
Skeleton,
SkeletonItem,
Empty,
Expand Down Expand Up @@ -297,6 +299,7 @@ export default {
Avatar,
Drawer,
Popconfirm,
Affix,
Skeleton,
SkeletonItem,
Empty,
Expand Down
15 changes: 15 additions & 0 deletions test/unit/specs/affix.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { createTest, destroyVM } from '../util';
import Affix from 'packages/affix';

describe('Affix', () => {
let vm;
afterEach(() => {
destroyVM(vm);
});

it('create', () => {
vm = createTest(Affix, true);
expect(vm.$el).to.exist;
});
});

16 changes: 16 additions & 0 deletions types/affix.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { ElementUIComponent } from './component'

/** Affix Component */
export declare class ElAffix extends ElementUIComponent {

offsetTop?: number

offsetBottom?: number

target?: () => Window | HTMLElement | null

targets?: () => Window[] | HTMLElement[] | null

useCapture?: boolean

}
Loading