记录一次vue2项目中拖动div时,div的高度会随着鼠标向上拖动变高

拖动组件:

<template>

    <div

      ref="file-list-win"

      class="file-list-win"

      v-draggable="{limit: true, left: 256, top: 0  }"

      :class="{ min: min == true }"

      style="bottom: 50px"

    >

    <!-- 注意: 如果有拖动的要求,bottom最好不要写在样式文件中,直接写在div的style中,拖动时会清除bottom样式 -->

        <!-- 头部 -->

        <div class="title dragTitle">

            <div>

                <i v-if="getIfAllDone == 1" class="el-icon-success success"></i>

                <div v-else class="loading">

                  <img :src="require('@/assets/images/management/file/loading.png')">

                </div>

            </div>

            <div class="msg">

                <p v-if="getIfAllDone == 1">上传 成功(<span class="success-color">{{ getSuccessCount }}</span>项) 失败(<span class="fail-color">{{ getFailCount }}</span>项)</p>

                <p v-else>正在上传 - 剩余 {{ fileList.length - getDoneCount }} 项</p>

            </div>

            <div class="btns">

                <el-button type="info" circle @click="close()">

                  <i class="el-icon-close"></i>

                </el-button>

                <el-button type="primary" circle v-if="!min" @click="clickSlide()">

                    <i class="el-icon-arrow-up"></i>

                </el-button>

                <el-button type="info" circle v-if="min" @click="clickSlide()">

                    <i class="el-icon-arrow-down"></i>

                </el-button>

            </div>

        </div>

        <div class="content">

            <ul>

                <li

                    v-for="(file, index) in fileList"

                    :key="`item.taskId-${index}`"

                >

                    <div class="bg" v-if="file.status == 'loading' && file.progress > 0" :style="`width: ${file.progress * 100}%`"></div>

                    <div class="bg" v-else style="width: 0"></div>

                    <div class="info">

                        <div class="tb">

                            <template v-if="file.status == 'loading'">

                              <img :src="require('@/assets/images/management/file/file.png')">

                            </template>

                            <template v-else>

                              <img v-if="file.ext =='mp4'" :src="require('@/assets/images/management/file/video.png')"/>

                              <img v-else-if="file.ext =='mp3'" :src="require('@/assets/images/management/file/video.png')"/>

                              <img v-else-if="file.ext =='docx' || file.ext =='doc'" :src="require('@/assets/images/management/file/word.png')"/>

                              <img v-else-if="file.ext =='pptx' || file.ext =='ppt'" :src="require('@/assets/images/management/file/file.png')"/>

                              <img v-else-if="file.ext =='xlsx' || file.ext =='xls'" :src="require('@/assets/images/management/file/excel.png')"/>

                              <img v-else-if="file.ext =='pdf'" :src="require('@/assets/images/management/file/pdf.png')"/>

                              <img v-else :src="require('@/assets/images/management/file/file.png')"/>

                            </template>

                        </div>

                        <div class="msg">

                            <p class="name">{{ file.name }}</p>

                            <p class="other">

                              <span>{{ file.size }}MB</span>

                              <span class="success" v-if="file.status=='success'">上传成功</span>

                              <span class="fail" v-if="file.status=='fail'">上传失败</span>

                            </p>

                        </div>

                    </div>

                </li>

            </ul>

        </div>

    </div>

</template>

<script>

import { mapState } from  'vuex';

export default {

    name: 'FileList',

    components: {},

    props: {

      fileList: {

        type: Array,

        require: true

      }

    },

    data () {

        return {

            min: false,

            height: 0   //存储div本身的高度,展开时计算用到

        };

    },

    computed: {

        // 计算完成的数量

        getDoneCount () {

            return this.fileList.filter(

                (item) => item.status == 'success' || item.status == 'fail'

            ).length;

        },

        // 计算全部导出任务是否完成

        getIfAllDone () {

            let doneCount = this.getDoneCount;

            return doneCount == this.fileList.length ? 1 : 2;

        },

        // 计算成功的数量

        getSuccessCount () {

            return this.fileList.filter(

                (item) => item.status == 'success'

            ).length;

        },

        // 计算失败的数量

        getFailCount () {

            return this.fileList.filter(

                (item) => item.status == 'fail'

            ).length;

        },

    },

    watch: {},

    mounted () {

    },

    methods: {

        // 收起展开

        clickSlide () {

          this.min = !this.min;

          if (!this.min) {

            // 展开

            if (this.$refs['file-list-win'].style.top) {

              // 移动过div

              let top = this.$refs['file-list-win'].style.top;

              let topValue = 0;

              if (top.indexOf('px') > -1) {

                let temp = top.substring(0, top.indexOf('px'));

                topValue = parseInt(temp);

              } else {

                topValue = parseInt(top);

              }

              let h1 = window.innerHeight;

              if (topValue + this.height > h1) {

                // 如果div本身的高度+离顶部的高度 大于 页面的高度,则重新计算div离顶部的高度

                let temp_top = h1 - this.height - 50;

                this.$refs['file-list-win'].style.top = temp_top+'px';

              }

            } else {

              // 未移动过div,不处理

            }

          } else {

            // 收起的时候存下div的高度

            this.height = this.$refs['file-list-win'].offsetHeight;

          }

        },

        // 关闭

        close () {

            if (this.getIfAllDone == 1) {

                this.$emit('closeFileList');

            } else {

                this.$message({

                    type: "warning",

                    message: "有正在进行的任务,请勿关闭!",

                });

            }

        },

    }

};

</script>

<style lang="scss" scoped>

.file-list-win {

    position: fixed;

    right: vw(15);

    // bottom: 50px

    width: vw(400);

    border-radius: 6px;

    z-index:999;

    background: #ffffff;

    box-shadow: rgba(0, 0, 0, 0.1) 0px 3px 5px, rgba(0, 0, 0, 0.06) 0px 6px 10px,

        rgba(0, 0, 0, 0.06) 0px 1px 16px;

    overflow: hidden;

    .title {

        display: flex;

        padding: vh(16) vw(24);

        line-height: vh(24);

        border-bottom: 1px solid #e9e9e9;

        .success {

            color: #66b165;

            font-size: 22px;

            margin-top: 2px;

        }

        .loading {

            width: vw(30);

            height: vw(30);

            img {

              position: relative;

              top: -2px;

              width: 100%;

              height: 100%;

            }

        }

        .msg {

            flex: 1;

            margin-left: vw(10);

            .total {

                color: #909399;

            }

        }

        .btns {

            margin-top: -1px;

            .el-button--medium.is-circle {

                padding: 2px;

            }

            i {

                cursor: pointer;

                font-size: 20px;

            }

        }

    }

    .content {

        max-height: vh(380);

        overflow: auto;

        ul {

            padding: 0;

            li {

                position: relative;

                margin-bottom: vh(10);

                .bg {

                  height: vh(70);

                  background: #F5F5F6;

                  border-bottom: 2px solid #1890FF;

                  z-index: 0;

                }

                .info {

                    position: absolute;

                    left: 0;

                    top: 0;

                    z-index: 1;

                    padding: vh(10) vw(20) vh(10) vw(30);

                    height: vh(50);

                    display: flex;

                    margin-bottom: vh(10);

                    // &:hover {

                    //     background-color: #f5f6f6;

                    // }

                    .tb {

                        width: vw(40);

                        height: vw(40);

                        line-height: vw(40);

                        background: #EAF5FF;

                        border-radius: 6px;

                        text-align: center;

                    }

                    .msg {

                        margin-left: vw(10);

                        flex: 1;

                        .name {

                            width: vw(240);

                            height: vh(30);

                            word-wrap: break-word; /*强制换行*/

                            overflow: hidden; /*超出隐藏*/

                            text-overflow: ellipsis; /*隐藏后添加省略号*/

                            white-space: nowrap; /*强制不换行*/

                            margin: 0;

                        }

                        .other {

                            color: #999999;

                            margin: 0;

                            .success {

                                color: #20A80D;

                                margin-left: vw(10);

                            }

                            .fail {

                                color: red;

                                margin-left: vw(10);

                            }

                        }

                    }

                    .btns {

                        i {

                            cursor: pointer;

                            margin-left: vw(10);

                        }

                        .percent {

                            margin-left: vw(10);

                        }

                    }

                }

            }

        }

    }

    .success-color {

      color: #20A80D;

    }

    .fail-color {

      color: red;

    }

}

.min {

    height: vh(60);

    .content {

        display: none;

    }

}

</style>

拖动指令:

export default {

  install (Vue) {

        // 拖动指令,绑定在div上,

        // 被绑定的div的css样式不要有bottom属性,如果初始化需要用到bottom定位,请直接写在div的style中

        // binding 传值可选 {limit: true, left: 256, top: 0 }

        // limit - 有范围限制,如传入false,可能会拖到父级DOM元素外

        // left - 拖动div离父级DOM左侧最小距离

        // top - 拖动div离父级DOM上侧侧最小距离

        Vue.directive('draggable', {

          // 当被绑定的元素挂载到 DOM 上时...

          bind (el, binding, vnode) {

            const dragTitleEl = el.querySelector('.dragTitle'); // 获取弹窗标题栏元素

            dragTitleEl.style.cssText += ';cursor:move;'

            console.log('binding: ', binding);

            // 添加鼠标按下事件监听

            el.onmousedown = function(e) {

              // 获取鼠标点击位置相对于元素左上角的x,y坐标

              let disX = e.clientX - el.offsetLeft;

              let disY = e.clientY - el.offsetTop;

              // 添加鼠标移动事件监听

              document.onmousemove = function(e) {

                // 计算元素的新位置

                let l = e.clientX - disX;

                let t = e.clientY - disY;

                // 限制拖拽范围

                if (binding.value.limit) {

                  let limit_l = binding.value.left || 0;

                  let limit_t = binding.value.top || 0;

                  if (l < limit_l) {

                    l = limit_l;

                  }

                  if (t < limit_t) {

                    t = limit_t;

                  }

                  if (l > window.innerWidth - el.offsetWidth) {

                    l = window.innerWidth - el.offsetWidth;

                  }

                  if (t > window.innerHeight - el.offsetHeight) {

                    t = window.innerHeight - el.offsetHeight;

                  }

                }

                // 清空之前的样式

                el.style.cssText = "";

                // 设置元素的位置

                el.style.left = l + 'px';

                el.style.top = t + 'px';

              };

              // 添加鼠标抬起事件监听,用于停止拖拽

              document.onmouseup = function(e) {

                document.onmousemove = null;

                document.onmouseup = null;

              };

            };

          }

        })

    }

}

出现错误的原因:

由于最初要求div是定位到右下角,所以使用了bottom属性(且写在样式文件中),在拖动计算过程中,只计算left 和 top属性,所以向上拖动时,由于bottom属性一直存在,div离下边缘的距离不变,但是left属性使div离上边缘的距离变了,就出现了把div高度拖高的问题。

解决方法:

1、bottom直接写在div的style中;

2、拖动过程中使用el.style.cssText = "";  清空样式。

<think>我们正在实现一个Vue组件,该组件包含两个div,并且可以通过拖动它们之间的分隔条来调整各自的宽度。参考引用[1]中提供了HTML结构和CSS样式,我们可以借鉴其基本思路,并利用Vue来实现数据绑定和事件处理。基本思路:1.组件结构:左侧div、可拖动的分隔条、右侧div2.使用CSS浮动布局,分隔条作为中间的可拖动元素。3.在Vue中,我们需要动态绑定左侧div和右侧div的宽度(通过计算属性或data属性)。4.为分隔条添加鼠标事件(mousedown,mousemove, mouseup)来实现拖动效果。步骤:1.在组件的模板中,按照结构创建三个部分:左侧、分隔条、右侧。2.使用CSS设置基本样式,特别是分隔条的样式(如背景色、光标形状等)。3.在Vue组件的data中定义初始宽度(例如leftWidth,可以设置为百分比)。4.在分隔条上监听mousedown事件,当鼠标按下记录开始位置,并监听mousemove和mouseup事件。5.在mousemove事件中,根据鼠标移动的偏移量计算新的左侧宽度(并更新leftWidth),同更新右侧宽度(通常右侧宽度为100%减去左侧宽度和分隔条宽度)。6.注意在mouseup移除mousemove和mouseup事件。注意:由于拖动过程中,鼠标可能会移出分隔条,因此mousemove和mouseup事件应该绑定在document上。另外,为了用户体验,在拖动过程中可以给整个分隔条添加一个样式(比如改背景色),并在拖动结束后移除。我们将创建一个Vue组件,命名为ResizablePanels。代码实现如下:</think>### Vue实现可拖动调整Div宽度的组件 在Vue中实现可拖动调整div宽度的组件,主要通过以下步骤完成: #### 1. 组件结构 ```vue <template> <div class="resizable-container"> <div class="left-panel" :style="{ width: leftWidth + 'px' }"> <!-- 左侧内容 --> </div> <div class="resize-handle" @mousedown="startResize"> <!-- 拖动--> </div> <div class="right-panel"> <!-- 右侧内容(自动填充剩余宽度) --> </div> </div> </template> ``` #### 2. 核心逻辑 ```vue <script> export default { data() { return { leftWidth: 300, // 初始宽度 isResizing: false, startX: 0, startWidth: 0 } }, methods: { startResize(e) { this.isResizing = true this.startX = e.clientX this.startWidth = this.leftWidth // 添加全局事件监听 document.addEventListener('mousemove', this.resize) document.addEventListener('mouseup', this.stopResize) }, resize(e) { if (!this.isResizing) return // 计算新的宽度 const offsetX = e.clientX - this.startX this.leftWidth = Math.max(100, this.startWidth + offsetX) // 最小宽度100px }, stopResize() { this.isResizing = false // 移除事件监听 document.removeEventListener('mousemove', this.resize) document.removeEventListener('mouseup', this.stopResize) } } } </script> ``` #### 3. CSS样式 ```css .resizable-container { display: flex; height: 100vh; overflow: hidden; } .left-panel { height: 100%; background: #f5f7fa; overflow: auto; } .resize-handle { width: 10px; background: #dcdfe6; cursor: col-resize; transition: background 0.3s; &:hover { background: #c0c4cc; } } .right-panel { flex: 1; height: 100%; background: #fff; overflow: auto; } ``` ### 实现说明 1. **拖动原理**: - 鼠标按下记录初始位置 (`startX`) 和初始宽度 (`startWidth`) - 鼠标移动计算偏移量 `offsetX = e.clientX - startX` - 新宽度 = 初始宽度 + 偏移量 (`startWidth + offsetX`) - 使用 `Math.max()` 设置最小宽度限制 2. **注意事项**: - 事件监听需绑定到 `document` 确保拖动过程中鼠标移出组件区域仍有效 - 移除事件监听防止内存泄漏 - 添加最小宽度限制防止面板过小 - 使用 `flex` 布局确保右侧面板自动填充剩余空间 3. **优化建议**: - 添加过渡动画:`transition: width 0.2s ease` - 支持双向拖动:调整逻辑即可实现左右双向调整 - 响应式处理:添加窗口大小监听重置默认宽度 - 添加拖拽的视觉反馈(如改光标样式) ### 使用示例 ```vue <template> <ResizablePanels> <template #left> <div>左侧内容区域</div> </template> <template #right> <div>右侧内容区域</div> </template> </ResizablePanels> </template> <script> import ResizablePanels from './ResizablePanels.vue' export default { components: { ResizablePanels } } </script> ``` 这个组件实现了基础的拖动调整宽度功能,核心代码约40行,通过事件处理和CSS布局完成。可根据需要扩展为可复用组件,支持垂直分隔、最小/最大宽度限制等特性[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值