VUE 侧边按钮组,可自定义位置

  • 有图标:收缩 → 显示图标(居中)
  • 无图标 :收缩 → 首字居中显示
  • 展开时 :无论有没有图标,都只显示 图标 + 完整文字 / 完整文字
  • 动画流畅、hover 变色、点击事件正常
html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>侧边按钮 - 首字居中</title>
    <script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        body {
            background: #f6f7f9;
            height: 200vh;
            padding: 50px;
            font-family: "Microsoft Yahei", sans-serif;
        }
        .demo-text {
            max-width: 800px;
            line-height: 2;
            color: #333;
            font-size: 16px;
        }
    </style>
</head>
<body>
<div id="app">
    <div class="demo-text">
        <h2>侧边按钮演示</h2>
        <p>✅ 无图标收缩:首字【居中显示】</p>
        <p>✅ 有图标收缩:只显示图标</p>
        <p>✅ 鼠标展开:显示完整文字</p>
    </div>

    <side-buttons
            position="right"
            :top="220"
            :offset="0"
            :trigger-width="30"
            :buttons="btnList"
    ></side-buttons>
</div>

<script>
    const { createApp, ref, computed } = Vue

    const SideButtons = {
        props: {
            position: {
                type: String,
                default: 'right',
                validator: val => ['left', 'right'].includes(val)
            },
            top: { type: Number, default: 200 },
            offset: { type: Number, default: 0 },
            triggerWidth: { type: Number, default: 30 },
            buttons: { type: Array, default: () => [] }
        },
        setup(props) {
            const isExpand = ref(false)

            const triggerStyle = computed(() => {
                return {
                    position: 'fixed',
                    top: props.top + 'px',
                    zIndex: 999,
                    [props.position]: '0',
                    width: props.triggerWidth + 'px',
                    height: '360px'
                }
            })

            const wrapperStyle = computed(() => {
                return {
                    position: 'fixed',
                    top: props.top + 'px',
                    [props.position]: props.offset + 'px',
                    zIndex: 999
                }
            })

            const getFirstChar = (str) => str ? str.charAt(0) : ''

            return { isExpand, triggerStyle, wrapperStyle, getFirstChar }
        },
        template: `
          <div
              class="side-trigger"
              :style="triggerStyle"
              @mouseenter="isExpand = true"
              @mouseleave="isExpand = false"
          >
            <div class="side-buttons" :style="wrapperStyle">
              <div
                  class="side-btn"
                  :class="{ expand: isExpand }"
                  v-for="(item, idx) in buttons"
                  :key="idx"
                  @click="item.onClick && item.onClick()"
              >
                <!-- 图标 -->
                <span class="btn-icon" v-if="item.icon">{{ item.icon }}</span>

                <!-- 无图标 + 收起时:首字居中 -->
                <span class="btn-short-text" v-if="!item.icon && !isExpand">
            {{ getFirstChar(item.text) }}
          </span>

                <!-- 展开时:完整文字 -->
                <span class="btn-full-text" v-if="isExpand">{{ item.text }}</span>
              </div>
            </div>
          </div>
        `
    }

    createApp({
        components: { SideButtons },
        setup() {
            const btnList = ref([
                { icon: '🏠', text: '首页', onClick: () => alert('首页') },
                { icon: '📞', text: '在线客服', onClick: () => alert('客服') },
                { icon: '', text: '产品中心', onClick: () => alert('产品中心') },
                { icon: '', text: '关于我们', onClick: () => alert('关于我们') },
                { icon: '🔝', text: '回到顶部', onClick: () => window.scrollTo({ top: 0, behavior: 'smooth' }) }
            ])
            return { btnList }
        }
    }).mount('#app')
</script>

<style>
    .side-buttons {
        display: flex;
        flex-direction: column;
        gap: 8px;
    }
    .side-btn {
        width: 48px;
        height: 48px;
        background: #fff;
        border-radius: 6px 0 0 6px;
        box-shadow: 0 2px 12px rgba(0, 0, 0, 0.12);
        display: flex;
        align-items: center;
        justify-content: center; /* 关键:居中 */
        padding: 0;
        cursor: pointer;
        transition: all 0.3s ease;
        overflow: hidden;
        white-space: nowrap;
    }
    .side-btn.expand {
        width: 130px;
        justify-content: flex-start; /* 展开后左对齐 */
        padding: 0 12px;
    }
    .side-btn:hover {
        background: #409eff;
        color: #fff;
    }

    /* 图标 */
    .btn-icon {
        font-size: 20px;
        flex-shrink: 0;
    }

    /* 首字:居中 */
    .btn-short-text {
        font-size: 16px;
        display: flex;
        align-items: center;
        justify-content: center;
        width: 100%;
        height: 100%;
    }

    /* 展开文字 */
    .btn-full-text {
        font-size: 14px;
        margin-left: 0;
    }

    /* 左侧适配 */
    [position="left"] .side-btn {
        border-radius: 0 6px 6px 0;
    }
</style>
</body>
</html>
相关推荐
2分钟速写快排16 分钟前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
passerby60611 小时前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角2 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!2 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰2 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
mayaairi4 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen4 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
风骏时光牛马5 小时前
AI源码分析:拆解模型底层实现逻辑
前端
BillKu5 小时前
全局样式变量:CSS 自定义属性(--border-color)和SCSS 变量($border-color)的说明
javascript·css·scss
IT_陈寒5 小时前
React子组件莫名其妙重渲染?你可能漏了这个Hook
前端·人工智能·后端