【sgOvalMenu】Componente personalizado: menú elíptico, el botón de menú puede moverse circularmente junto con la pista elíptica

característica:

  1. Puede establecer el ancho y el alto de la pista de elipse. 
  2. Se puede configurar el ángulo de rotación de la pista de elipse y se puede corregir automáticamente el estado horizontal del texto del menú.
  3. Puede configurar el paso de movimiento de las coordenadas de la trayectoria del movimiento.
  4. Puede configurar la pista de movimiento para cambiar la frecuencia.
  5. Se puede configurar para que gire en el sentido de las agujas del reloj o en el sentido contrario a las agujas del reloj.
  6. Puede configurar si el botón de entrada detiene el botón de movimiento circular

Código fuente de sgOvalMenu 

<template>
    <div :class="$options.name" :border-animate="borderAnimate" :style="style">
        <div class="ovalMenuBtn" v-for="(a, i) in menubtns" :key="i" :style="a.style" @click="$emit(`click`, a);"
            @mouseover="mouseover(a)" @mouseout="mouseout(a)">
            <slot :data="a"></slot>
        </div>
    </div>
</template>
<script>
export default {
    name: 'sgOvalMenu',
    data() {
        return {
            style: {},
            coordinates: [],
            step_: 0,//按钮在椭圆轨道上面移动的步长
            time_: 0,//按钮坐标变化的时间间隔
            rotate_: 0,//椭圆旋转角度
            oval_step: 1,//椭圆动画步长
            interval1: null,
            interval2: null,
            menubtns: [],
            isHoverBtn: false,
            borderAnimate: true,
        }
    },
    props: [
        "width",//椭圆的长直径
        "height",//椭圆的短直径
        "rotate",//椭圆旋转角度
        "step",//按钮在椭圆轨道上面移动的步长
        "time",//按钮坐标变化的时间间隔
        "clockwise",//顺时针运动(boolean)
        "hoverButtonPause",//移入按钮暂停运动(boolean)
        "data",//椭圆上面的按钮数据
    ],
    watch: {
        width: {
            handler(d) {
                this.style.width = `${d || 800}px`;
            }, deep: true, immediate: true,
        },
        height: {
            handler(d) {
                this.style.height = `${d || 400}px`;
            }, deep: true, immediate: true,
        },
        rotate_: {
            handler(d) {
                this.style.rotate = `${d || 0}deg`;
                this.setProperty();
            }, deep: true, immediate: true,
        },
        rotate: {
            handler(d) {
                this.rotate_ = d;
            }, deep: true, immediate: true,
        },
        step: {
            handler(d) {
                this.step_ = d || 2;
            }, deep: true, immediate: true,
        },
        time: {
            handler(d) {
                this.time_ = d || 200;
            }, deep: true, immediate: true,
        },
        data: {
            handler(d) {
                if (d) {
                    this.menubtns = JSON.parse(JSON.stringify(d));
                    this.getCoordinates(d => {
                        this.coordinates = d;
                        this.initAnimate();
                    });
                }
            }, deep: true, immediate: true,
        },
        isHoverBtn(newValue, oldValue) {
            if (this.hoverButtonPause || this.hoverButtonPause === '') {
                newValue ? this.clearIntervalAll() : this.initAnimate();
                this.borderAnimate = !newValue;
            }
        },
    },
    destroyed() {
        this.clearIntervalAll();
    },
    mounted() {
        this.setProperty();
    },
    methods: {
        mouseover(d) {
            this.isHoverBtn = true;
            this.$emit(`mouseover`, d);
        },
        mouseout(d) {
            this.isHoverBtn = false;
            this.$emit(`mouseout`, d);
        },
        setProperty() {
            this.$el && this.$el.style.setProperty("--rotate", `${-1 * parseFloat(this.style.rotate || 0)}deg`); //js往css传递局部参数
        },
        clearIntervalAll(d) {
            clearInterval(this.interval1);
            clearInterval(this.interval2);
        },
        initAnimate(d) {
            this.initAnimateBtn();
            this.initAnmiateOval()
        },
        // 按钮旋转动画
        initAnimateBtn() {
            clearInterval(this.interval1);
            this.interval1 = setInterval(() => {
                this.setStyles();
            }, this.time_);
            this.setStyles();
        },
        // 椭圆旋转动画
        initAnmiateOval(d) {
            clearInterval(this.interval2);
            this.interval2 = setInterval(() => {
                this.rotate_ = this.rotate_ + this.oval_step;
                this.rotate_ > this.rotate && (this.oval_step = -1);
                this.rotate_ < -1 * this.rotate && (this.oval_step = 1);
            }, 382);
        },
        setStyles() {
            let coordinateStep = this.coordinates.length / this.menubtns.length;
            let arr = this.coordinates, N = this.step_;
            if (this.clockwise || this.clockwise === '') {
                //前面N个元素挪到最后
                arr.splice(arr.length - 1, 0, ...arr.splice(0, N));
            } else {
                //最后N个元素挪到前面
                arr.splice(0, 0, ...arr.splice(arr.length - N));
            }
            this.coordinates = arr;
            this.menubtns.forEach((v, i) => {
                let coordinate = this.coordinates[i * coordinateStep];
                this.$set(v, "style", {
                    left: `${coordinate.x}px`,
                    top: `${coordinate.y}px`,
                });
            });
        },
        getCoordinates(cb) {
            let a = parseFloat(this.style.width) / 2;
            let b = parseFloat(this.style.height) / 2;
            this.getCPoint(a, b, 1, a, b, cb);
        },
        // a 长半径, b 短半径, p 节点的间隔 , cx, cy 圆心, 
        getCPoint(a, b, p = 1, cx = 0, cy = 0, cb) {
            const data = []
            for (let index = 0; index < 360; index = index + p) {
                let x = a * Math.cos(Math.PI * 2 * index / 360)
                let y = b * Math.sin(Math.PI * 2 * index / 360)
                data.push({ x: x + cx, y: y + cy })
            }
            cb && cb(data);
        },
    }
};
</script>    
<style lang="scss" scoped>
$rotate: var(--rotate);

.sgOvalMenu {
    border: 2px dashed transparent;
    border-color: #409EFF66 #409EFFAA #409EFF #409EFF;
    border-radius: 100%;
    width: 100%;
    height: 100%;
    transform-origin: center;
    transition: .382s linear;

    .ovalMenuBtn {
        transition: .382s linear;
        user-select: none;
        white-space: nowrap;
        position: absolute;
        width: max-content;
        height: max-content;
        left: 0;
        top: 0;
        transform: translate(-50%, -50%) rotate($rotate);
        transform-origin: center;
        pointer-events: auto;
        color: white;
        cursor: pointer;

        &:hover {
            z-index: 1;
            font-weight: bold;
            color: #409EFF;
            text-shadow: 0px 0px 5px #409EFF;
            filter: brightness(1.1);
        }
    }

    /*边框虚线滚动动画特效*/
    &[border-animate] {
        background: linear-gradient(90deg, #409EFF 60%, transparent 60%) repeat-x left top/10px 1px,
            linear-gradient(0deg, #409EFF 60%, transparent 60%) repeat-y right top/1px 10px,
            linear-gradient(90deg, #409EFF 60%, transparent 60%) repeat-x right bottom/10px 1px,
            linear-gradient(0deg, #409EFF 60%, transparent 60%) repeat-y left bottom/1px 10px;

        animation: border-animate .382s infinite linear;

        @keyframes border-animate {
            0% {
                background-position: left top, right top, right bottom, left bottom;
            }

            100% {
                background-position: left 10px top, right top 10px, right 10px bottom, left bottom 10px;
            }
        }
    }
}
</style>

solicitud

<template>
  <div :class="$options.name">
    <!-- 椭圆菜单 -->
    <sgOvalMenu :data="ovalMenus" @click="clickOvalMenu" :width="700" :height="200" :rotate="30" clockwise
      hoverButtonPause>
      <template v-slot="{ data }">
        <div class="btn">
          {
   
   { data.label }}
        </div>
      </template>
    </sgOvalMenu>
  </div>
</template>
<script>
import sgOvalMenu from "./sgOvalMenu";
export default {
  name: 'sgBody',
  components: { sgOvalMenu },
  data() {
    return {
      ovalMenus: [
        { value: '1', label: '显示文本1', },
        { value: '2', label: '显示文本2', },
        { value: '3', label: '显示文本3', },
        { value: '4', label: '显示文本4', },
        { value: '5', label: '显示文本5', },
      ],
    }
  },
  methods: {
    clickOvalMenu(d) {
      // console.log(`获取点击信息:`, JSON.stringify(d, null, 2));
    },
  }
};
</script>
<style lang="scss" scoped>
.sgBody {
  display: flex;
  justify-content: center;
  align-items: center;
  background-color: black;

  .btn {
    box-sizing: border-box;
    padding: 10px 20px;
    border-radius: 88px;
    box-sizing: border-box;
    border: 1px solid #409EFF;
    box-shadow: 0 10px 30px #409EFFAA, 0 10px 30px #409EFF99 inset;
    color: #409EFF;

    &:hover {
      box-shadow: 0 10px 30px #409EFFAA, 0 10px 30px #409EFF99 inset;
      background-color: #409EFF;
      color: black;
      filter: brightness(1.3);
    }
  }
}
</style>

Supongo que te gusta

Origin blog.csdn.net/qq_37860634/article/details/132517527
Recomendado
Clasificación