123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474 |
- <template>
- <view
- :class="['s-popup',positionClass,visibleClass,effectClass,customClass]"
- :style="styleZindex+styleDuration"
- >
- <template v-if="mask">
- <view
- class="s-popup-mask"
- @touchmove.stop.prevent="e=>e.preventDefault()"
- @click="maskClose && hide()"
- :style="'background-color: rgba(0, 0, 0, '+maskOpacity+');'"
- ></view>
- </template>
- <view
- v-if="preventTouchmove"
- class="s-popup-wrapper"
- @touchmove.stop.prevent="e=>e.preventDefault()"
- >
- <slot></slot>
- </view>
- <view v-else class="s-popup-wrapper">
- <slot></slot>
- </view>
- </view>
- </template>
- <script>
- // 记录弹框的zIndex
- const ZindexMap = new Map();
- const getMaxZindex = () => {
- return Math.max(200, ...ZindexMap.values()) + 1;
- };
- export default {
- name: 's-popup',
- props: {
- // class
- customClass: {
- type: String,
- default: ''
- },
- // v-model双向绑定
- value: Boolean,
- // 弹框显示位置 center | left | right | top | bottom
- position: {
- type: String,
- default: 'center'
- },
- // 是否使用过渡效果
- effect: {
- type: Boolean,
- default: true
- },
- // 过渡时间
- effectDuration: {
- type: Number,
- default: 300
- },
- // 是否显示遮罩
- mask: {
- type: Boolean,
- default: true
- },
- // 遮罩透明度
- maskOpacity: {
- type: Number,
- default: 0.7
- },
- // 点击遮罩是否关闭弹框
- maskClose: {
- type: Boolean,
- default: true
- },
- // 自动关闭时间
- duration: {
- type: Number,
- default: 0
- },
- // 是否阻止弹层touchmove滚动,手机上滚动穿透
- preventTouchmove: {
- type: Boolean,
- default: false
- },
- // 显示时拦截钩子返回promise拦截
- beforeShow: Function,
- // 隐藏时拦截钩子返回promise拦截
- beforeHide: Function
- },
- data () {
- Object.assign(this, {
- popupId: 's-popup-id-' + Math.random().toString(36).substr(2),
- visibleId: 0,
- visibleStatus: false,
- effectTimeoutId: 0,
- autoCloseTimeoutId: 0
- });
- return {
- styleZindex: '',
- visibleClass: '',
- styleDuration: '',
- effectClass: ''
- };
- },
- computed: {
- positionClass () {
- return this.position ? 's-popup-position-' + this.position : '';
- }
- },
- watch: {
- value () {
- this.updateVisible();
- }
- },
- methods: {
- async show () {
- if (!this.visibleStatus) {
- this.visibleId++;
- let status = true;
- const nowId = this.visibleId;
- if (this.beforeShow) {
- try {
- await this.beforeShow.call(this.$parent);
- } catch (error) {
- status = false;
- }
- }
- if (nowId === this.visibleId) {
- if (status) {
- const effectDuration = this.effect ? this.effectDuration : 0;
- this.visibleStatus = true;
- this.$emit('input', true);
- ZindexMap.set(this.popupId, getMaxZindex());
- this.styleZindex = `z-index:${ZindexMap.get(this.popupId)};`;
- this.styleDuration = `animation-duration:${effectDuration}ms;`;
- this.visibleClass = 's-popup-visible';
- this.effectClass = 's-popup-effect-enter';
- clearTimeout(this.effectTimeoutId);
- this.effectTimeoutId = setTimeout(() => {
- this.styleDuration = '';
- this.effectClass = '';
- if (this.visibleStatus) {
- this.$emit('show');
- // 自动关闭
- const duration = parseInt(this.duration);
- if (duration > 0) {
- clearTimeout(this.autoCloseTimeoutId);
- this.autoCloseTimeoutId = setTimeout(() => {
- this.visibleStatus && this.hide();
- }, duration);
- }
- }
- }, effectDuration);
- } else {
- this.$emit('input', false);
- }
- }
- }
- },
- async hide () {
- if (this.visibleStatus) {
- this.visibleId++;
- let status = true;
- const nowId = this.visibleId;
- if (this.beforeHide) {
- try {
- await this.beforeHide.call(this.$parent);
- } catch (error) {
- status = false;
- }
- }
- if (nowId === this.visibleId) {
- if (status) {
- const effectDuration = this.effect ? this.effectDuration : 0;
- this.visibleStatus = false;
- this.$emit('input', false);
- this.styleDuration = 'animation-duration:' + effectDuration + 'ms;';
- this.effectClass = 's-popup-effect-leave';
- clearTimeout(this.autoCloseTimeoutId);
- clearTimeout(this.effectTimeoutId);
- this.effectTimeoutId = setTimeout(() => {
- this.visibleClass = '';
- this.effectClass = '';
- this.styleZindex = '';
- this.styleDuration = '';
- ZindexMap.delete(this.popupId);
- if (!this.visibleStatus) {
- this.$emit('hide');
- }
- }, effectDuration);
- } else {
- this.$emit('input', true);
- }
- }
- }
- },
- updateVisible () {
- this.$nextTick(() => {
- if (this.visibleStatus !== this.value) {
- this[this.value ? 'show' : 'hide']();
- }
- });
- }
- },
- mounted () {
- this.updateVisible();
- },
- beforeDestroy () {
- ZindexMap.delete(this.popupId);
- clearTimeout(this.effectTimeoutId);
- clearTimeout(this.autoCloseTimeoutId);
- }
- };
- </script>
- <style lang="scss">
- // fade效果
- @keyframes s-popup-fade-enter {
- 0% {
- opacity: 0;
- }
- to {
- opacity: 1;
- }
- }
- @keyframes s-popup-fade-leave {
- 0% {
- opacity: 1;
- }
- to {
- opacity: 0;
- }
- }
- // 显示和隐藏效果
- @keyframes s-popup-center-enter {
- 0% {
- opacity: 0;
- transform: scale(0.7);
- }
- to {
- opacity: 1;
- }
- }
- @keyframes s-popup-center-leave {
- 0% {
- opacity: 1;
- }
- to {
- opacity: 0;
- transform: scale(0.9);
- }
- }
- @keyframes s-popup-top-enter {
- 0% {
- transform: translate3d(0, -100%, 0);
- }
- to {
- transform: translate3d(0, 0, 0);
- }
- }
- @keyframes s-popup-top-leave {
- to {
- transform: translate3d(0, -100%, 0);
- }
- }
- @keyframes s-popup-left-enter {
- 0% {
- transform: translate3d(-100%, 0, 0);
- }
- to {
- transform: translate3d(0, 0, 0);
- }
- }
- @keyframes s-popup-left-leave {
- to {
- transform: translate3d(-100%, 0, 0);
- }
- }
- @keyframes s-popup-right-enter {
- 0% {
- transform: translate3d(100%, 0, 0);
- }
- to {
- transform: translate3d(0, 0, 0);
- }
- }
- @keyframes s-popup-right-leave {
- to {
- transform: translate3d(100%, 0, 0);
- }
- }
- @keyframes s-popup-bottom-enter {
- 0% {
- transform: translate3d(0, 100%, 0);
- }
- to {
- transform: translate3d(0, 0, 0);
- }
- }
- @keyframes s-popup-bottom-leave {
- to {
- transform: translate3d(0, 100%, 0);
- }
- }
- .s-popup {
- display: none;
- position: fixed;
- left: 0;
- right: 0;
- top: var(--window-top);
- bottom: var(--window-bottom);
- margin: 0;
- padding: 0;
- box-sizing: border-box;
- pointer-events: none;
- &-visible {
- display: flex;
- }
- &-mask {
- position: absolute;
- left: 0;
- top: 0;
- right: 0;
- bottom: 0;
- z-index: 1;
- pointer-events: auto;
- }
- &-wrapper {
- position: absolute;
- max-width: 100%;
- max-height: 100%;
- z-index: 2;
- pointer-events: auto;
- background-color: white;
- overflow: auto;
- }
- // 弹框效果
- animation-fill-mode: both;
- &-mask,
- &-wrapper {
- animation-fill-mode: both;
- animation-duration: inherit;
- }
- &-effect-enter {
- .s-popup-mask,
- .s-popup-wrapper {
- animation-name: s-popup-fade-enter;
- }
- }
- &-effect-leave {
- .s-popup-mask,
- .s-popup-wrapper {
- animation-name: s-popup-fade-leave;
- }
- }
- }
- //center
- .s-popup-position-center {
- justify-content: center;
- align-items: center;
- .s-popup-wrapper {
- position: relative;
- }
- &.s-popup-effect-enter {
- .s-popup-wrapper {
- animation-name: s-popup-center-enter;
- }
- }
- &.s-popup-effect-leave {
- .s-popup-wrapper {
- animation-name: s-popup-center-leave;
- }
- }
- }
- //top
- .s-popup-position-top {
- .s-popup-wrapper {
- left: 0;
- right: 0;
- top: 0;
- }
- &.s-popup-effect-enter {
- .s-popup-wrapper {
- animation-name: s-popup-top-enter;
- }
- }
- &.s-popup-effect-leave {
- .s-popup-wrapper {
- animation-name: s-popup-top-leave;
- }
- }
- }
- //bottom
- .s-popup-position-bottom {
- .s-popup-wrapper {
- left: 0;
- right: 0;
- bottom: 0;
- }
- &.s-popup-effect-enter {
- .s-popup-wrapper {
- animation-name: s-popup-bottom-enter;
- }
- }
- &.s-popup-effect-leave {
- .s-popup-wrapper {
- animation-name: s-popup-bottom-leave;
- }
- }
- }
- //从左侧滑入
- .s-popup-position-left {
- .s-popup-wrapper {
- left: 0;
- top: 0;
- bottom: 0;
- }
- &.s-popup-effect-enter {
- .s-popup-wrapper {
- animation-name: s-popup-left-enter;
- }
- }
- &.s-popup-effect-leave {
- .s-popup-wrapper {
- animation-name: s-popup-left-leave;
- }
- }
- }
- //从右侧滑入
- .s-popup-position-right {
- .s-popup-wrapper {
- right: 0;
- top: 0;
- bottom: 0;
- }
- &.s-popup-effect-enter {
- .s-popup-wrapper {
- animation-name: s-popup-right-enter;
- }
- }
- &.s-popup-effect-leave {
- .s-popup-wrapper {
- animation-name: s-popup-right-leave;
- }
- }
- }
- </style>
|