前端有道 前端有道
导航
算法
开发
  • Git

    • Git导读
  • 收藏夹 (opens new window)
  • 工具库 (opens new window)
  • netlify Vuepress (opens new window)
  • vercel Vuepress (opens new window)
  • Vuepress2.0 (opens new window)
  • netlify Vuepress2.0 (opens new window)
留言区
娱乐
关于
  • 时间轴
  • 标签
  • 分类

星野

给岁月以文明
导航
算法
开发
  • Git

    • Git导读
  • 收藏夹 (opens new window)
  • 工具库 (opens new window)
  • netlify Vuepress (opens new window)
  • vercel Vuepress (opens new window)
  • Vuepress2.0 (opens new window)
  • netlify Vuepress2.0 (opens new window)
留言区
娱乐
关于
  • 时间轴
  • 标签
  • 分类
  • 基础知识

  • 工程化

  • 组件库

    • 手写el-form表单组件
    • Vue Toast组件开发
    • Vue button组件开发
    • Vue icon组件开发
    • Vue input组件开发
    • Vue message组件开发
    • Vue Popover组件开发
  • CSS

  • ES6-ES12

  • JavaScript

  • Vue2

  • Vue3

  • webpack

  • 浏览器

  • 开发
  • 组件库
星野
2021-5-29
0

Vue Popover组件开发


<template>
  <div class="lcx-popover">
    <div
      class="lcx-popover-content"
      :class="`content-${placement}`"
      v-if="visible"
      ref="content"
      @click.stop
    >
      <h3>{{ title }}</h3>
      {{ content }}
      <i class="arrow"></i>
    </div>
    <slot name="reference"></slot>
  </div>
</template>
<script>
const on = (element, event, handle) => {
  element.addEventListener(event, handle, false);
};
const off = (element, event, handle) => {
  element.removeEventListener(event, handle, false);
};
export default {
  name: 'lcx-popover',
  data() {
    return {
      visible: false,
    };
  },
  watch: {
    visible(val) {
      if (val) {
        this.$nextTick(() => {
          let content = this.$refs.content;
          document.body.appendChild(content);
          if (this.trigger === 'hover') {
            on(content, 'mouseenter', this.handleMouseEnter);
            on(content, 'mouseleave', this.handleMouseLeave);
          }
        });
      }
    },
  },
  props: {
    title: {
      type: String,
    },
    trigger: {
      type: String,
    },
    width: {
      type: String,
    },
    content: {
      type: String,
    },
    placement: {
      type: String,
      validator(type) {
        if (!['top', 'left', 'right', 'botton'].includes(type)) {
          throw new Error(
            '属性必须是' + ['top', 'left', 'right', 'botton'].join('、')
          );
        }
        return true;
      },
    },
  },
  methods: {
    handleToggle() {
      this.visible = !this.visible;
    },
    handleDocumentToggle(e) {
      if (this.$el.contains(e.target)) return;
      this.visible = false;
    },
    handleMouseEnter() {
      clearTimeout(this.timer);
      this.visible = true;
    },
    handleMouseLeave() {
      this.timer = setTimeout(() => {
        this.visible = false;
      }, 200);
    },
  },
  mounted() {
    let reference = this.$slots.reference;
    if (reference) {
      this.reference = reference[0].elm;
    }
    if (this.trigger === 'hover') {
      on(this.$el, 'mouseenter', this.handleMouseEnter);
      on(this.$el, 'mouseleave', this.handleMouseLeave);
    } else if (this.trigger === 'click') {
      on(this.reference, 'click', this.handleToggle);
      on(document, 'click', this.handleDocumentToggle);
    }
  },
  beforeDestroy() {
    off(this.$el, 'mouseenter', this.handleMouseEnter);
    off(this.$el, 'mouseleave', this.handleMouseLeave);
    off(this.reference, 'click', this.handleToggle);
    off(document, 'click', this.handleDocumentToggle);
  },
};
</script>
<style lang="scss" scoped>
.lcx-popover {
  display: inline-block;
  position: relative;
  &-content {
    position: absolute;
    top: 100px;
    left: 30px;
    padding: 15px;
    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
    background-color: #fff;
  }
}
.arrow {
  &::before {
    position: absolute;
    display: block;
    content: '';
    width: 0;
    height: 0;
    border: 10px solid transparent;
    border-bottom-color: #fff;
    filter: drop-shadow(0 -2px 1px #eee);
  }
  // &::after {
  //   border-bottom-color: #fff;
  //   transform: translateY(1px);
  //   filter: drop-shadow(0 -2px 1px #ccc);
  // }
}
.content-top{
  .arrow {
    &::before {
      top: -19px;
      left: 50%;
      margin-left: -10px;
    }
  }
}
.content-bottom{
  .arrow {
    &::before {
      bottom: -19px;
      left: 50%;
      margin-left: -10px;
      transform: rotate(-180deg);
    }
  }
}
.content-left{
  .arrow {
    &::before {
      top: 50%;
      left: -19px;
      margin-top: -10px;
      transform: rotate(-90deg);
    }
  }
}
.content-right{
  .arrow {
    &::before {
      top: 50%;
      right: -19px;
      margin-top: -10px;
      transform: rotate(90deg);
    }
  }
}
</style>
#vue
上次更新: 2022/05/09, 06:48:29
Vue message组件开发
CSS小技巧

← Vue message组件开发 CSS小技巧→

最近更新
01
图解Git
05-10
02
关于 - 网站错误反馈
05-10
03
关于 - 赞赏❤️的用途
05-10
更多文章>
加入前端有道交流群 | Copyright © 2018-2025 星野 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式