A recent project migrated to a new project built on the Vue framework, but the project does not use the Vue UI library and does not yet encapsulate the common popover components. So I implemented a simple popover component. Consider the following points before developing:

  1. Component title, button copy, button number and popup contents can be customized;

  2. The vertical horizontal center of the popover takes into account the space occupied by the bottom return button in the ios wechat environment and the fact that the head is not available in the wechat environment.

  3. The mask layer is separated from the popover content. Click the mask layer to close the popover.

  4. If multiple popovers appear at the same time, the popover’s Z-index should be higher than the previous one;

  5. Click on the mask layer to close the popover and process the page content at the bottom of the popover without scrolling.

It contains the main features to be implemented and the problems to be addressed.

Implementation steps

  1. Complete the page structure and style and transition animation
  2. Customize popup titles, buttons, and theme content
  3. Switch components
  4. Z – index processing
  5. Click on the mask layer to close the popover
  6. The page content at the bottom of the pop-up window is not scrollable

1. Complete the page structure and style

Create a popover component Vue file to implement the basic structure and style.

<template>
    <div class="dialog">
        <div class="dialog-mark"></div>
        <transition name="dialog">
            <div class="dialog-sprite"> <! -- Title --> <section v-if="title" class="header"> Temporary title </section> <! -- Popover theme content --> <section class="dialog-body"> Temporary content </section> <! Button --> <section class="dialog-footer">
                    <div class="btn btn-confirm"> confirm < / div > < / section > < / div > < / transition > < / div > < / template > < script >export default {
        data() {return {}
        }
    }
</srcipt>


<style lang="less"Scoped > // popover animation.dialoc-enter-active,.dialoc-leave-active {transition: animation.5s; } .dialog-enter, .dialog-leave-to { opacity: 0; } // Set position on the outermost layer // mask on the background layer, z-index must be large enough to ensure coverage, height width is set to full screen mask. Dialog {position: fixed; top: 0; right: 0; width: 100%; height: 100%; // The content layer z-index must be larger than the mask, otherwise it will be covered. Dialog-mark {position: absolute; top: 0; height: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, .6); }. Dialog-sprite {// Mobile use felx layout position: absolute; top: 10%; left: 15%; right: 15%; bottom: 25%; display: flex; flex-direction: column; max-height: 75%; min-height: 180px; overflow: hidden; z-index: 23456765435; background:#fff;
            border-radius: 8px;
            .header {
                padding: 15px;
                text-align: center;
                font-size: 18px;
                font-weight: 700;
                color: # 333;
            }
            .dialog-body {
                flex: 1;
                overflow-x: hidden;
                overflow-y: scroll;
                padding: 0 15px 20px 15px;
            }
            .dialog-footer {
                position: relative;
                display: flex;
                width: 100%;
                // flex-shrink: 1;
                &::after {
                    content: ' ';
                    position: absolute;
                    top: 0;
                    left: 0;
                    width: 100%;
                    height: 1px;
                    background: #ddd;
                    transform: scaleY(.5);
                }
                .btn {
                    flex: 1;
                    text-align: center;
                    padding: 15px;
                    font-size: 17px;
                    &:nth-child(2) {
                        position: relative;
                        &::after {
                            content: ' ';
                            position: absolute;
                            left: 0;
                            top: 0;
                            width: 1px;
                            height: 100%;
                            background: #ddd;
                            transform: scaleX(.5);
                        }
                    }
                }
                .btn-confirm {
                    color: #43ac43;
                }
            }
        }
    }
</style>
Copy the code

2. Customize popup titles, buttons, and themes

By omitting the style code, we set the title to customizable and required.

Buttons default to display a confirm button, and can customize the confirm button copy, and can display and customize the cancel button copy, and their click event handling.

You are advised to use slot for topic content. If you are not sure about slot, go to the vue website to learn about slot.

<template>
    <div class="dialog">
        <div class="dialog-mark"></div>
        <transition name="dialog">
            <div class="dialog-sprite"> <! -- Title --> <section v-if="title" class="header">{{ title }}</section> <! -- Popover theme content --> <section class="dialog-body"> <slot></slot> </section> <! Button --> <section class="dialog-footer">
                    <div v-if="showCancel" class="btn btn-refuse" @click="cancel">{{cancelText}}</div>
                    <div class="btn btn-confirm" @click="confirm">{{confirmText}}</div>
                </section>
            </div>
        </transition>
    </div>
</template>

<script>
    export default {
        props: {
            title: String,
            showCancel: {
                typs: Boolean,
                default: false,
                required: false,
            },
            cancelText: {
                type: String,
                default: 'cancel',
                required: false,
            },
            confirmText: {
                type: String,
                default: 'sure',
                required: false,}},data() {
            return {
                name: 'dialog',}},... Methods: {/** Cancel button operation */cancel() {
                this.$emit('cancel'.false); }, /** Confirm button operation */confirm() {
                this.$emit('confirm'.false)
            },
        }
    }
</script>
Copy the code

3. Switch components

The switch of the popover component is externally controlled, but not directly controlled using show. Instead, it listens on show and assigns a value to the component’s internal variable showSelf.

This also makes it easier to hide the component’s internal control pop-ups. Clicking on the mask layer to close the popover is handled in this way below.

<template> <div v-if="showSelf" class="dialog" :style="{'z-index': zIndex}">
    </div>
</template>

<script>
    exportDefault {props: {// Whether to display popover components by default, mandatory properties are not displayed.type: Boolean,
                default: false,
                required: true,}},data() {
            return {
                showSelf: false,
            }
        },
        watch: {
            show(val) {
                if(! val) { this.closeMyself() }else {
                    this.showSelf = val
                }
            }
        },
        created() {
            this.showSelf = this.show;
        },
    }
</script>
Copy the code

4. Z – index processing

First of all, we need to ensure that the level of the popover component z-INDE is high enough, and secondly, we need to ensure that the level of the popover content is higher than the level of the popover mask.

Popovers that pop up later have a higher level than those that pop up earlier. (Not fully guaranteed)

<template>
    <div v-if="showSelf" class="dialog" :style="{'z-index': zIndex}">
        <div class="dialog-mark" :style="{'z-index': zIndex + 1}"></div>
        <transition name="dialog">
            <div class="dialog-sprite" :style="{'z-index': zIndex + 2}">... </div> </transition> </div> </template> <script>export default {
        data() {
            return{zIndex: this.getzIndex (),}}, methods: {/** zIndex automatically increases */ after each fetchgetZIndex() {
                let zIndexInit = 20190315;
                return zIndexInit++
            },
        }
    }
</script>
Copy the code

5. Click the mask layer to close the popover and make the page content at the bottom of the popover unscrollable

The important thing to note here is that after the component is mounted, setting overflow to hidden for the body prevents the page from scrolling under the pop-up window.

When clicking on the mask layer, we can hide the popover component inside the component. V-if is also the destruction of the component when hidden.

<template>
    <div v-if="showSelf" class="dialog" :style="{'z-index': zIndex}">
        <div class="dialog-mark" @click.self="closeMyself" :style="{'z-index': zIndex + 1}"></div>
    </div>
</template>

<script>
    export default {
        data() {
            return {
                zIndex: this.getZIndex(),
            }
        },
        mounted() {this.forbidScroll()}, methods: {/** forbid page scroll */forbidScroll() {
                this.bodyOverflow = document.body.style.overflow
                document.body.style.overflow = 'hidden'}, /** closeMyself(event) {this.showself =false; This.slovebodyoverflow ()}, /** Restore the page scroll */sloveBodyOverflow() {
                document.body.style.overflow = this.bodyOverflow;
            },
        }
    }
</script>

Copy the code

The final effect of the component

The final complete component code looks like this:

<template>
    <div v-if="showSelf" class="dialog" :style="{'z-index': zIndex}">
        <div class="dialog-mark" @click.self="closeMyself" :style="{'z-index': zIndex + 1}"></div>
        <transition name="dialog">
            <div class="dialog-sprite" :style="{'z-index': zIndex + 2}"> <! -- Title --> <section v-if="title" class="header">{{ title }}</section> <! -- Popover theme content --> <section class="dialog-body"> <slot></slot> </section> <! Button --> <section class="dialog-footer">
                    <div v-if="showCancel" class="btn btn-refuse" @click="cancel">{{cancelText}}</div>
                    <div class="btn btn-confirm" @click="confirm">{{confirmText}}</div>
                </section>
            </div>
        </transition>
    </div>
</template>

<script>
    exportDefault {props: {// Whether to display popover components by default, mandatory properties are not displayed.type: Boolean,
                default: false,
                required: true,
            },
            title: {
                type: String,
                required: true,
            },
            showCancel: {
                typs: Boolean,
                default: false,
                required: false,
            },
            cancelText: {
                type: String,
                default: 'cancel',
                required: false,
            },
            confirmText: {
                type: String,
                default: 'sure',
                required: false,}},data() {
            return {
                name: 'dialog',
                showSelf: false,
                zIndex: this.getZIndex(),
                bodyOverflow: ' '
            }
        },
        watch: {
            show(val) {
                if(! val) { this.closeMyself() }else {
                    this.showSelf = val
                }
            }
        },
        created() {
            this.showSelf = this.show;
        },
        mounted() {this.forbidScroll()}, methods: {/** forbid page scroll */forbidScroll() {
                this.bodyOverflow = document.body.style.overflow
                document.body.style.overflow = 'hidden'}, /** ZIndex automatically increments */ after each fetchgetZIndex() {
                let zIndexInit = 20190315;
                returnZIndexInit++}, /** cancel button operation */cancel() {
                this.$emit('cancel'.false); }, /** Confirm button operation */confirm() {
                this.$emit('confirm'.false}, /** Close the popover */ closeMyself(event) {this.showself =false; This.slovebodyoverflow ()}, /** Restore the page scroll */sloveBodyOverflow() {
                document.body.style.overflow = this.bodyOverflow;
            },
        }
    }
</script>

<style lang="less"Scoped > // popover animation.dialoc-enter-active,.dialoc-leave-active {transition: animation.5s; } .dialog-enter, .dialog-leave-to { opacity: 0; } // Set position on the outermost layer // mask on the background layer, z-index must be large enough to ensure coverage, height width is set to full screen mask. Dialog {position: fixed; top: 0; right: 0; width: 100%; height: 100%; // The content layer z-index must be larger than the mask, otherwise it will be covered. Dialog-mark {position: absolute; top: 0; height: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, .6); }}. Dialog-sprite {// Use felx layout position: absolute; top: 10%; left: 15%; right: 15%; bottom: 25%; display: flex; flex-direction: column; max-height: 75%; min-height: 180px; overflow: hidden; z-index: 23456765435; background:#fff;
        border-radius: 8px;
        .header {
            padding: 15px;
            text-align: center;
            font-size: 18px;
            font-weight: 700;
            color: # 333;
        }
        .dialog-body {
            flex: 1;
            overflow-x: hidden;
            overflow-y: scroll;
            padding: 0 15px 20px 15px;
        }
        .dialog-footer {
            position: relative;
            display: flex;
            width: 100%;
            // flex-shrink: 1;
            &::after {
                content: ' ';
                position: absolute;
                top: 0;
                left: 0;
                width: 100%;
                height: 1px;
                background: #ddd;
                transform: scaleY(.5);
            }
            .btn {
                flex: 1;
                text-align: center;
                padding: 15px;
                font-size: 17px;
                &:nth-child(2) {
                    position: relative;
                    &::after {
                        content: ' ';
                        position: absolute;
                        left: 0;
                        top: 0;
                        width: 1px;
                        height: 100%;
                        background: #ddd;
                        transform: scaleX(.5);
                    }
                }
            }
            .btn-confirm {
                color: #43ac43;
            }
        }
    }
</style>

Copy the code

use

  1. Introduce a popover component in the parent component
import TheDialog from './component/TheDialog'
Copy the code
  1. Components registered in the component
components: {
    TheDialog
}
Copy the code
  1. Used in template
<the-dialog :show="showDialog" @confirm="confirm2" @cancel="cancel" :showCancel="true" :title="' New title '" :confirmText="' Got it. '" :cancelText="` ` off"> < p > topic content < / p > < p > topic content < / p > < p > topic content < / p > < p > topic content < / p > < p > topic content < / p > < p > topic content < / p > < p > topic content < / p > < p > topic content < / p > < p > topic content < / p > < p > topic content < / p > < p > topic content < / p > < / the dialog - > < in the dialog: show ="showDialog2" @confirm="confirm2" :title="' Popover component title '" :confirmText="' Got it. '"> < p > topic content < / p > < p > topic content < / p > < p > topic content < / p > < p > topic content < / p > < p > topic content < / p > < p > topic content < / p > < p > topic content < / p > < p > topic content < / p > < p > topic content < / p > <p> <p> <p> </p> </the-dialog> <script>export default {
        data() {
            return{// Control the initial display and hiding of two popover components: showDialogtrue, 
                showDialog2: true,
            }
        },
        methods: {
            cancel(show) {
                this.showDialog = show
            },
            confirm(show) {
                this.showDialog = show
            },
            cancel2(show) {
                this.showDialog2 = show
            },
            confirm2(show) {
                this.showDialog2 = show;
            },
        }
    }
</script>
Copy the code

This article simply records the realization steps of a simple popover component. The main use of vue slot to accept popover content from the parent component; Props receives popover customization Settings from the parent component and controls the show and hide of popovers; The child component passes through the $emit listening event to the parent component for logical processing.

other

Look no further than the Regret Vue series, here: juejin.cn/post/684490…

Many Vue learning partners have serious knowledge fragmentation, so I organized a systematic series of Vue learning blogs. On the way of self-growth, I also hope to help more people make progress. Poke link