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>
相关推荐
Avan_菜菜3 小时前
AI 能写代码了,为什么我反而开始要求它先写文档?
前端·github·ai编程
JieE2126 小时前
LeetCode 226. 翻转二叉树|JS 递归超详细拆解,二叉树入门经典题
javascript·算法
JieE2126 小时前
LeetCode 104. 二叉树的最大深度|递归思路超详细拆解
javascript·算法
爱勇宝7 小时前
鸿蒙生态的下半场:开发者不只要能开发,还要能赚钱
android·前端·程序员
IT_陈寒10 小时前
SpringBoot这个自动配置坑我跳了三次
前端·人工智能·后端
kyriewen10 小时前
我用 AI 一周写完了整个项目,上线第一天就崩了——这是我踩过最贵的 5 个坑
前端·javascript·ai编程
Larcher11 小时前
AI Loop:让AI像人一样自主完成任务的核心机制
javascript·人工智能·设计模式
默_笙11 小时前
🃏 JS 只有 8 种数据类型,但我花了 2 天才搞懂 null 和 undefined 的区别
javascript
牧艺11 小时前
从零到协同:构建类飞书在线文档系统的五个技术重难点
前端·人工智能
jump_jump11 小时前
流式 HTML:从 htmx 片段装配到浏览器原生增量渲染
javascript·性能优化·前端工程化