mirror of
https://gitee.com/vant-contrib/vant.git
synced 2025-08-07 04:59:46 +08:00
[Improvement] Tab animation fluency && position (#379)
This commit is contained in:
parent
2327e75516
commit
5a17bc520a
@ -20,12 +20,12 @@
|
|||||||
|
|
||||||
- 45+ Reusable components
|
- 45+ Reusable components
|
||||||
- 90%+ Unit test coverage
|
- 90%+ Unit test coverage
|
||||||
- Extensive documentation and demos.
|
- Extensive documentation and demos
|
||||||
- Support [babel-plugin-import](https://github.com/ant-design/babel-plugin-import)
|
- Support [babel-plugin-import](https://github.com/ant-design/babel-plugin-import)
|
||||||
- Support TypeScript
|
- Support TypeScript
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img alt="feature demo" src="https://img.yzcdn.cn/public_files/2017/12/05/6a69f80c7ba6754795a7cc6d0766950f.png">
|
<img alt="components preview" src="https://img.yzcdn.cn/public_files/2017/12/05/95f5ee7524b7845abb2f51803a01d65e.png">
|
||||||
</p >
|
</p >
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
- 支持 TypeScript
|
- 支持 TypeScript
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img alt="feature demo" src="https://img.yzcdn.cn/public_files/2017/12/05/6a69f80c7ba6754795a7cc6d0766950f.png">
|
<img alt="components preview" src="https://img.yzcdn.cn/public_files/2017/12/05/95f5ee7524b7845abb2f51803a01d65e.png">
|
||||||
</p >
|
</p >
|
||||||
|
|
||||||
## 安装
|
## 安装
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<demo-section>
|
<demo-section>
|
||||||
<demo-block :title="$t('basicUsage')">
|
<demo-block :title="$t('basicUsage')">
|
||||||
<van-tabs :active="active">
|
<van-tabs :active="active">
|
||||||
<van-tab :title="$t('tab') + index" v-for="index in 4" :key="index">
|
<van-tab :title="$t('tab') + index" v-for="index in tabs" :key="index">
|
||||||
{{ $t('content') }} {{ index }}
|
{{ $t('content') }} {{ index }}
|
||||||
</van-tab>
|
</van-tab>
|
||||||
</van-tabs>
|
</van-tabs>
|
||||||
@ -64,7 +64,8 @@ export default {
|
|||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
active: 2
|
active: 2,
|
||||||
|
tabs: [1, 2, 3, 4]
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -106,7 +106,7 @@ export default {
|
|||||||
|-----------|-----------|-----------|-------------|-------------|
|
|-----------|-----------|-----------|-------------|-------------|
|
||||||
| type | There are two style tabs, set this attribute to change tab style | `String` | `line` | `card` |
|
| type | There are two style tabs, set this attribute to change tab style | `String` | `line` | `card` |
|
||||||
| active | Index of active tab | `String` `Number` | `0` | - |
|
| active | Index of active tab | `String` `Number` | `0` | - |
|
||||||
| duration | Toggle tab's animation time | `Number` | `0.3` | - | - |
|
| duration | Toggle tab's animation time | `Number` | `0.2` | - | - |
|
||||||
| swipeThreshold | Set swipe tabs threshold | `Number` | `4` | - | - |
|
| swipeThreshold | Set swipe tabs threshold | `Number` | `4` | - | - |
|
||||||
|
|
||||||
### Tab API
|
### Tab API
|
||||||
|
@ -106,7 +106,7 @@ export default {
|
|||||||
|-----------|-----------|-----------|-------------|-------------|
|
|-----------|-----------|-----------|-------------|-------------|
|
||||||
| type | Tab 样式类型 | `String` | `line` | `card` |
|
| type | Tab 样式类型 | `String` | `line` | `card` |
|
||||||
| active | 默认激活的 tab | `String` `Number` | `0` | - |
|
| active | 默认激活的 tab | `String` `Number` | `0` | - |
|
||||||
| duration | 切换 tab 的动画时间 | `Number` | `0.3` | - | - |
|
| duration | 切换 tab 的动画时间 | `Number` | `0.2` | - | - |
|
||||||
| swipeThreshold | 滚动阀值,设置 Tab 超过多少个可滚动 | `Number` | `4` | - | - |
|
| swipeThreshold | 滚动阀值,设置 Tab 超过多少个可滚动 | `Number` | `4` | - | - |
|
||||||
|
|
||||||
### Tab API
|
### Tab API
|
||||||
|
@ -72,7 +72,6 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import { isServer } from '../../utils';
|
|
||||||
import Popup from '../../popup';
|
import Popup from '../../popup';
|
||||||
import Toast from '../../toast';
|
import Toast from '../../toast';
|
||||||
import SkuHeader from '../components/SkuHeader';
|
import SkuHeader from '../components/SkuHeader';
|
||||||
@ -177,7 +176,7 @@ export default {
|
|||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
bodyStyle() {
|
bodyStyle() {
|
||||||
if (isServer) {
|
if (this.$isServer) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="van-tab__pane" :class="{ 'van-tab__pane--select': key === $parent.curActive }">
|
<div class="van-tab__pane" :class="{ 'van-tab__pane--select': index === parentGroup.curActive }">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -20,46 +20,19 @@ export default {
|
|||||||
disabled: Boolean
|
disabled: Boolean
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
computed: {
|
||||||
|
index() {
|
||||||
|
return this.parentGroup.tabs.indexOf(this);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
this.findParentByName('van-tabs');
|
this.findParentByName('van-tabs');
|
||||||
const nextIndex = this.parentGroup.tabs.length;
|
this.parentGroup.tabs.push(this);
|
||||||
this.updateParentData(nextIndex);
|
|
||||||
return {
|
|
||||||
key: nextIndex
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
watch: {
|
|
||||||
title() {
|
|
||||||
this.updateParentData();
|
|
||||||
},
|
|
||||||
disabled() {
|
|
||||||
this.updateParentData();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
updateParentData(nextIndex) {
|
|
||||||
const index = arguments.length ? nextIndex : this.key;
|
|
||||||
this.parentGroup.tabs.splice(index, 1, {
|
|
||||||
title: this.title,
|
|
||||||
disabled: this.disabled,
|
|
||||||
index
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
destroyed() {
|
destroyed() {
|
||||||
const key = this.key;
|
this.parentGroup.tabs.splice(this.index, 1);
|
||||||
const tabs = this.parentGroup.tabs;
|
|
||||||
|
|
||||||
for (let i = 0; i < tabs.length; i++) {
|
|
||||||
/* istanbul ignore else */
|
|
||||||
if (tabs[i].index === key) {
|
|
||||||
this.parentGroup.tabs.splice(i, 1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,39 +1,23 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="van-tabs" :class="`van-tabs--${type}`">
|
<div class="van-tabs" :class="`van-tabs--${type}`">
|
||||||
<div class="van-tabs__nav-wrap" v-if="type === 'line' && tabs.length > swipeThreshold">
|
<div :class="{ 'van-tabs__swipe': scrollable, 'van-hairline--top-bottom': type === 'line' }">
|
||||||
<div class="van-tabs__swipe" ref="swipe">
|
<div class="van-tabs__nav" :class="`van-tabs__nav--${type}`" ref="nav">
|
||||||
<div class="van-tabs__nav van-tabs__nav--line">
|
<div v-if="type === 'line'" class="van-tabs__nav-bar" :style="navBarStyle" />
|
||||||
<div class="van-tabs__nav-bar" :style="navBarStyle"></div>
|
<div
|
||||||
<div
|
v-for="(tab, index) in tabs"
|
||||||
v-for="(tab, index) in tabs"
|
:key="index"
|
||||||
:key="index"
|
ref="tabs"
|
||||||
class="van-tab van-hairline"
|
class="van-tab"
|
||||||
:class="{ 'van-tab--active': index === curActive }"
|
:class="{
|
||||||
ref="tabkey"
|
'van-tab--active': index === curActive,
|
||||||
@click="handleTabClick(index)"
|
'van-tab--disabled': tab.disabled
|
||||||
>
|
}"
|
||||||
<span>{{ tab.title }}</span>
|
@click="onClick(index)"
|
||||||
</div>
|
>
|
||||||
|
<span>{{ tab.title }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
v-else
|
|
||||||
class="van-tabs__nav"
|
|
||||||
:class="`van-tabs__nav--${type}`"
|
|
||||||
>
|
|
||||||
<div class="van-tabs__nav-bar" :style="navBarStyle" v-if="type === 'line'"></div>
|
|
||||||
<div
|
|
||||||
v-for="(tab, index) in tabs"
|
|
||||||
:key="index"
|
|
||||||
class="van-tab van-hairline"
|
|
||||||
:class="{ 'van-tab--active': index === curActive }"
|
|
||||||
ref="tabkey"
|
|
||||||
@click="handleTabClick(index)"
|
|
||||||
>
|
|
||||||
<span>{{ tab.title }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="van-tabs__content">
|
<div class="van-tabs__content">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
@ -41,192 +25,126 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import swipe from './swipe';
|
import { raf } from '../utils/raf';
|
||||||
import translateUtil from '../utils/transition';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'van-tabs',
|
name: 'van-tabs',
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
// 外部传入的激活的tab标签
|
active: {
|
||||||
active: {
|
type: [Number, String],
|
||||||
type: [Number, String],
|
default: 0
|
||||||
default: 0
|
},
|
||||||
},
|
type: {
|
||||||
// 是默认的line还是card
|
type: String,
|
||||||
type: {
|
default: 'line'
|
||||||
type: String,
|
},
|
||||||
default: 'line'
|
duration: {
|
||||||
},
|
type: Number,
|
||||||
// 切换tab的动画时间
|
default: 0.2
|
||||||
duration: {
|
},
|
||||||
type: Number,
|
swipeThreshold: {
|
||||||
default: 0.3
|
type: Number,
|
||||||
},
|
default: 4
|
||||||
swipeThreshold: {
|
}
|
||||||
type: Number,
|
},
|
||||||
default: 4
|
|
||||||
}
|
data() {
|
||||||
|
this.winWidth = this.$isServer ? 0 : window.innerWidth;
|
||||||
|
|
||||||
|
return {
|
||||||
|
tabs: [],
|
||||||
|
curActive: 0,
|
||||||
|
navBarStyle: {}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
active(val) {
|
||||||
|
this.correctActive(val);
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
tabs(tabs) {
|
||||||
return {
|
this.correctActive(this.curActive);
|
||||||
tabs: [],
|
this.setNavBar();
|
||||||
curActive: +this.active,
|
|
||||||
isSwiping: false,
|
|
||||||
isInitEvents: false,
|
|
||||||
navBarStyle: {}
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
curActive() {
|
||||||
active(val) {
|
this.scrollIntoView();
|
||||||
this.curActive = +val;
|
this.setNavBar();
|
||||||
},
|
}
|
||||||
|
},
|
||||||
|
|
||||||
curActive() {
|
mounted() {
|
||||||
this.setNavBarStyle();
|
this.correctActive(this.active);
|
||||||
/* istanbul ignore else */
|
this.setNavBar();
|
||||||
if (this.tabs.length > this.swipeThreshold) {
|
},
|
||||||
this.doOnValueChange();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
tabs(val) {
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.setNavBarStyle();
|
|
||||||
if (val.length > this.swipeThreshold) {
|
|
||||||
this.initEvents();
|
|
||||||
this.doOnValueChange();
|
|
||||||
} else {
|
|
||||||
this.isInitEvents = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const activeExist = val.some(tab => tab.index === this.curActive);
|
computed: {
|
||||||
if (!activeExist) {
|
scrollable() {
|
||||||
this.curActive = val[0].index || 0;
|
return this.tabs.length > this.swipeThreshold;
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
methods: {
|
||||||
swipeWidth() {
|
setNavBar() {
|
||||||
return this.$refs.swipe && this.$refs.swipe.getBoundingClientRect().width;
|
|
||||||
},
|
|
||||||
maxTranslate() {
|
|
||||||
/* istanbul ignore if */
|
|
||||||
if (!this.$refs.tabkey) return;
|
|
||||||
|
|
||||||
const lastTab = this.$refs.tabkey[this.tabs.length - 1];
|
|
||||||
const lastTabWidth = lastTab.offsetWidth;
|
|
||||||
const lastTabOffsetLeft = lastTab.offsetLeft;
|
|
||||||
|
|
||||||
return (lastTabOffsetLeft + lastTabWidth) - this.swipeWidth;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
mounted() {
|
|
||||||
// 页面载入完成
|
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.setNavBarStyle();
|
const tab = this.$refs.tabs[this.curActive];
|
||||||
|
this.navBarStyle = {
|
||||||
if (this.tabs.length > this.swipeThreshold) {
|
width: `${tab.offsetWidth || 0}px`,
|
||||||
this.initEvents();
|
transform: `translate3d(${tab.offsetLeft || 0}px, 0, 0)`,
|
||||||
this.doOnValueChange();
|
transitionDuration: `${this.duration}s`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
correctActive(active) {
|
||||||
/**
|
active = +active;
|
||||||
* `type`为`line`时,tab下方的横线的样式
|
const exist = this.tabs.some(tab => tab.index === active);
|
||||||
*/
|
this.curActive = exist ? active : (this.tabs[0].index || 0);
|
||||||
setNavBarStyle() {
|
},
|
||||||
if (this.type !== 'line' || !this.$refs.tabkey) return {};
|
|
||||||
|
|
||||||
const tabKey = this.curActive;
|
|
||||||
const elem = this.$refs.tabkey[tabKey];
|
|
||||||
const offsetWidth = `${elem.offsetWidth || 0}px`;
|
|
||||||
const offsetLeft = `${elem.offsetLeft || 0}px`;
|
|
||||||
|
|
||||||
this.navBarStyle = {
|
|
||||||
width: offsetWidth,
|
|
||||||
transform: `translate3d(${offsetLeft}, 0, 0)`,
|
|
||||||
transitionDuration: `${this.duration}s`
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
handleTabClick(index) {
|
|
||||||
if (this.tabs[index].disabled) {
|
|
||||||
this.$emit('disabled', index);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
onClick(index) {
|
||||||
|
if (this.tabs[index].disabled) {
|
||||||
|
this.$emit('disabled', index);
|
||||||
|
} else {
|
||||||
this.$emit('click', index);
|
this.$emit('click', index);
|
||||||
this.curActive = index;
|
this.curActive = index;
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将当前value值转换为需要translate的值
|
|
||||||
*/
|
|
||||||
value2Translate(value) {
|
|
||||||
/* istanbul ignore if */
|
|
||||||
if (!this.$refs.tabkey) return 0;
|
|
||||||
|
|
||||||
const tab = this.$refs.tabkey[value];
|
|
||||||
const maxTranslate = this.maxTranslate;
|
|
||||||
const tabWidth = tab.offsetWidth;
|
|
||||||
const tabOffsetLeft = tab.offsetLeft;
|
|
||||||
let translate = tabOffsetLeft + (tabWidth * 2.7) - this.swipeWidth;
|
|
||||||
if (translate < 0) {
|
|
||||||
translate = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1 * (translate > maxTranslate ? maxTranslate : translate);
|
|
||||||
},
|
|
||||||
|
|
||||||
initEvents() {
|
|
||||||
const el = this.$refs.swipe;
|
|
||||||
if (!el || this.isInitEvents) return;
|
|
||||||
|
|
||||||
this.isInitEvents = true;
|
|
||||||
let swipeState = {};
|
|
||||||
|
|
||||||
swipe(el, {
|
|
||||||
start: event => {
|
|
||||||
swipeState = {
|
|
||||||
start: new Date(),
|
|
||||||
startLeft: event.pageX,
|
|
||||||
startTranslateLeft: translateUtil.getElementTranslate(el).left
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
drag: event => {
|
|
||||||
this.isSwiping = true;
|
|
||||||
|
|
||||||
swipeState.left = event.pageX;
|
|
||||||
const deltaX = swipeState.left - swipeState.startLeft;
|
|
||||||
const translate = swipeState.startTranslateLeft + deltaX;
|
|
||||||
|
|
||||||
/* istanbul ignore else */
|
|
||||||
if (translate > 0 || (translate * -1) > this.maxTranslate) return;
|
|
||||||
|
|
||||||
translateUtil.translateElement(el, translate, null);
|
|
||||||
},
|
|
||||||
|
|
||||||
end: () => {
|
|
||||||
this.isSwiping = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
doOnValueChange() {
|
|
||||||
const value = +this.curActive;
|
|
||||||
const swipe = this.$refs.swipe;
|
|
||||||
|
|
||||||
translateUtil.translateElement(swipe, this.value2Translate(value), null);
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
scrollIntoView() {
|
||||||
|
if (!this.scrollable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tab = this.$refs.tabs[this.curActive];
|
||||||
|
const { nav } = this.$refs;
|
||||||
|
const { winWidth } = this;
|
||||||
|
const { scrollLeft } = nav;
|
||||||
|
const { offsetLeft, offsetWidth: tabWidth } = tab;
|
||||||
|
|
||||||
|
// out of right side
|
||||||
|
if ((winWidth + scrollLeft - offsetLeft - tabWidth * 1.8) < 0) {
|
||||||
|
this.scrollTo(nav, scrollLeft, offsetLeft + tabWidth * 1.8 - winWidth);
|
||||||
|
}
|
||||||
|
// out of left side
|
||||||
|
else if (offsetLeft < (scrollLeft + tabWidth * 0.8)) {
|
||||||
|
this.scrollTo(nav, scrollLeft, offsetLeft - tabWidth * 0.8);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
scrollTo(el, from, to) {
|
||||||
|
let count = 0;
|
||||||
|
const frames = Math.round(this.duration * 1000 / 16);
|
||||||
|
const animate = () => {
|
||||||
|
el.scrollLeft += (to - from) / frames;
|
||||||
|
if (++count < frames) {
|
||||||
|
raf(animate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
animate();
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,46 +0,0 @@
|
|||||||
import Vue from 'vue';
|
|
||||||
|
|
||||||
let isSwiping = false;
|
|
||||||
|
|
||||||
const supportTouch = !Vue.prototype.$isServer && 'ontouchstart' in window;
|
|
||||||
|
|
||||||
export default function(element, options) {
|
|
||||||
const moveFn = function(event) {
|
|
||||||
if (options.drag) {
|
|
||||||
options.drag(supportTouch ? event.changedTouches[0] || event.touches[0] : event);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const endFn = function(event) {
|
|
||||||
if (!supportTouch) {
|
|
||||||
document.removeEventListener('mousemove', moveFn);
|
|
||||||
document.removeEventListener('mouseup', endFn);
|
|
||||||
}
|
|
||||||
|
|
||||||
isSwiping = false;
|
|
||||||
|
|
||||||
if (options.end) {
|
|
||||||
options.end(supportTouch ? event.changedTouches[0] || event.touches[0] : event);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
element.addEventListener(supportTouch ? 'touchstart' : 'mousedown', function(event) {
|
|
||||||
if (isSwiping) return;
|
|
||||||
|
|
||||||
if (!supportTouch) {
|
|
||||||
document.addEventListener('mousemove', moveFn);
|
|
||||||
document.addEventListener('mouseup', endFn);
|
|
||||||
}
|
|
||||||
isSwiping = true;
|
|
||||||
|
|
||||||
if (options.start) {
|
|
||||||
options.start(supportTouch ? event.changedTouches[0] || event.touches[0] : event);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (supportTouch) {
|
|
||||||
element.addEventListener('touchmove', moveFn);
|
|
||||||
element.addEventListener('touchend', endFn);
|
|
||||||
element.addEventListener('touchcancel', endFn);
|
|
||||||
}
|
|
||||||
};
|
|
32
packages/utils/raf.js
Normal file
32
packages/utils/raf.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* requestAnimationFrame polyfill
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { isServer } from './index';
|
||||||
|
|
||||||
|
let prev = Date.now();
|
||||||
|
function fallback(fn) {
|
||||||
|
const curr = Date.now();
|
||||||
|
const ms = Math.max(0, 16 - (curr - prev));
|
||||||
|
const id = setTimeout(fn, ms);
|
||||||
|
prev = curr + ms;
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const global = isServer ? global : window;
|
||||||
|
const iRaf =
|
||||||
|
global.requestAnimationFrame ||
|
||||||
|
global.webkitRequestAnimationFrame ||
|
||||||
|
fallback;
|
||||||
|
const iCancel =
|
||||||
|
global.cancelAnimationFrame ||
|
||||||
|
global.webkitCancelAnimationFrame ||
|
||||||
|
global.clearTimeout;
|
||||||
|
|
||||||
|
export function raf(fn) {
|
||||||
|
return iRaf.call(global, fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function cancel(id) {
|
||||||
|
iCancel.call(global, id);
|
||||||
|
}
|
@ -1,73 +0,0 @@
|
|||||||
import Vue from 'vue';
|
|
||||||
|
|
||||||
var exportObj = {};
|
|
||||||
|
|
||||||
if (!Vue.prototype.$isServer) {
|
|
||||||
var cssPrefix = '-webkit-';
|
|
||||||
var vendorPrefix = 'Webkit';
|
|
||||||
var transformProperty = vendorPrefix + 'Transform';
|
|
||||||
var transformStyleName = cssPrefix + 'transform';
|
|
||||||
var transitionProperty = vendorPrefix + 'Transition';
|
|
||||||
var transitionStyleName = cssPrefix + 'transition';
|
|
||||||
var transitionEndProperty = vendorPrefix.toLowerCase() + 'TransitionEnd';
|
|
||||||
|
|
||||||
var getTranslate = function(element) {
|
|
||||||
var result = { left: 0, top: 0 };
|
|
||||||
if (element === null || element.style === null) return result;
|
|
||||||
|
|
||||||
var transform = element.style[transformProperty];
|
|
||||||
var matches = /translate\(\s*(-?\d+(\.?\d+?)?)px,\s*(-?\d+(\.\d+)?)px\)\s*translateZ\(0px\)/ig.exec(transform);
|
|
||||||
if (matches) {
|
|
||||||
result.left = +matches[1];
|
|
||||||
result.top = +matches[3];
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
var translateElement = function(element, x, y) {
|
|
||||||
if (x === null && y === null) return;
|
|
||||||
|
|
||||||
if (element === null || element === undefined || element.style === null) return;
|
|
||||||
|
|
||||||
if (!element.style[transformProperty] && x === 0 && y === 0) return;
|
|
||||||
|
|
||||||
if (x === null || y === null) {
|
|
||||||
var translate = getTranslate(element);
|
|
||||||
if (x === null) {
|
|
||||||
x = translate.left;
|
|
||||||
}
|
|
||||||
if (y === null) {
|
|
||||||
y = translate.top;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cancelTranslateElement(element);
|
|
||||||
element.style[transformProperty] += ' translate(' + (x ? (x + 'px') : '0px') + ',' + (y ? (y + 'px') : '0px') + ') translateZ(0px)';
|
|
||||||
};
|
|
||||||
|
|
||||||
var cancelTranslateElement = function(element) {
|
|
||||||
if (element === null || element.style === null) return;
|
|
||||||
|
|
||||||
var transformValue = element.style[transformProperty];
|
|
||||||
|
|
||||||
if (transformValue) {
|
|
||||||
transformValue = transformValue.replace(/translate\(\s*(-?\d+(\.?\d+?)?)px,\s*(-?\d+(\.\d+)?)px\)\s*translateZ\(0px\)/g, '');
|
|
||||||
element.style[transformProperty] = transformValue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
exportObj = {
|
|
||||||
transformProperty: transformProperty,
|
|
||||||
transformStyleName: transformStyleName,
|
|
||||||
transitionProperty: transitionProperty,
|
|
||||||
transitionStyleName: transitionStyleName,
|
|
||||||
transitionEndProperty: transitionEndProperty,
|
|
||||||
getElementTranslate: getTranslate,
|
|
||||||
translateElement: translateElement,
|
|
||||||
cancelTranslateElement: cancelTranslateElement
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default exportObj;
|
|
||||||
|
|
@ -4,46 +4,38 @@
|
|||||||
.van-tabs {
|
.van-tabs {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
&__nav-wrap {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__swipe {
|
&__swipe {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
transition: transform linear .2s;
|
|
||||||
|
|
||||||
.van-tab {
|
.van-tab {
|
||||||
flex: 0 0 22%;
|
flex: 0 0 22%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.van-tabs__nav {
|
.van-tabs__nav {
|
||||||
overflow: visible;
|
overflow-y: auto;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__nav {
|
&__nav {
|
||||||
overflow: hidden;
|
|
||||||
transition: transform .5s cubic-bezier(.645, .045, .355, 1);
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
background-color: $white;
|
||||||
|
|
||||||
&--line {
|
&--line {
|
||||||
height: 44px;
|
height: 44px;
|
||||||
|
|
||||||
.van-tab {
|
|
||||||
&::after {
|
|
||||||
border-width: 1px 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&--card {
|
&--card {
|
||||||
height: 28px;
|
height: 28px;
|
||||||
margin: 0 15px;
|
margin: 0 15px;
|
||||||
background-color: $white;
|
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
border: 1px solid $gray-darker;
|
border: 1px solid $gray-darker;
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
.van-tab {
|
.van-tab {
|
||||||
color: $gray-darker;
|
color: $gray-darker;
|
||||||
@ -55,8 +47,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.van-tab--active {
|
&.van-tab--active {
|
||||||
background-color: $gray-darker;
|
|
||||||
color: $white;
|
color: $white;
|
||||||
|
background-color: $gray-darker;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -64,27 +56,25 @@
|
|||||||
|
|
||||||
&__nav-bar {
|
&__nav-bar {
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
left: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
height: 2px;
|
height: 2px;
|
||||||
background-color: #f13e3a;
|
position: absolute;
|
||||||
transition: transform .3s cubic-bezier(.645, .045, .355, 1);
|
background-color: $red;
|
||||||
transform-origin: 0 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.van-tab {
|
.van-tab {
|
||||||
|
flex: 1;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0 5px;
|
||||||
|
font-size: 14px;
|
||||||
position: relative;
|
position: relative;
|
||||||
color: $text-color;
|
color: $text-color;
|
||||||
background-color: $white;
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 44px;
|
line-height: 44px;
|
||||||
box-sizing: border-box;
|
|
||||||
cursor: pointer;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
flex: 1;
|
box-sizing: border-box;
|
||||||
padding: 0 5px;
|
background-color: $white;
|
||||||
min-width: 0; /* hack for flex ellipsis */
|
min-width: 0; /* hack for flex ellipsis */
|
||||||
|
|
||||||
span {
|
span {
|
||||||
@ -92,10 +82,22 @@
|
|||||||
@mixin ellipsis;
|
@mixin ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: $active-color;
|
||||||
|
}
|
||||||
|
|
||||||
&--active {
|
&--active {
|
||||||
color: $red;
|
color: $red;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&--disabled {
|
||||||
|
color: $gray;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: $white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&__pane {
|
&__pane {
|
||||||
display: none;
|
display: none;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user