[TOC]
>[success] # 正式分装章节
~~~
1.上个章节讲了一下思路,这个章节会封装一个splitpane的布局组件
~~~
>[danger] ##### 基础面板
~~~
1.根据上章节封装一个基础面板
~~~
~~~
<template>
<div class="split-pane-wrapper">
<div class="pane pane-left" :style="{width:leftOffsetPercent}">
<button @click="handleClick">点击减少左侧宽度</button>
</div>
<div class="pane-trigger-con" :style="{left:triggerLeft, width: `${triggerWidht}px`}"></div>
<div class="pane pane-right" :style="{left:leftOffsetPercent}"></div>
</div>
</template>
<script>
export default {
name: "split-pane",
props:{
triggerWidht:{
type:Number,
default:8,
},
},
data(){
return{
// 在这定义一个值。这样用户可以直接指定占比的值
// 在页面css 布局使用的值 使用计算属性拼接即可
leftOffset:0.3,
}
},
methods:{
handleClick(){
this.leftOffset -= 0.02
}
},
computed:{
// 动态属性去拼接生成css 实际需要的代%形式的数据
leftOffsetPercent(){
return `${this.leftOffset * 100}%`
},
triggerLeft(){
return `calc(${this.leftOffset * 100}% - ${this.triggerWidht/2}px)`
},
},
}
</script>
<style lang="less">
.split-pane-wrapper{
width: 100%;
height: 100%;
position: relative;
.pane{
position: absolute;
height: 100%;
top:0;
&-left{
/*width: 30%;*/
background: brown;
}
&-right{
right: 0;
bottom: 0;
/*left: 30%;*/
background: chartreuse;
}
&-trigger-con{
z-index: 100;
height: 100%;
background: red;
position: absolute;
top: 0;
}
}
}
</style>
~~~
>[info] ## 增加事件
~~~
1.接下来需要做通过给红色条添加事件,可以通过数据去操作布局
2.了解几个方法'event.pageX' -- 鼠标x轴的坐标距离(准确或是)
3.'this.$refs.outer.getBoundingClientRect()' -- 获取绑定dom元素的一些
距离浏览器的基本信息:
DOMRect {x: 8, y: 8, width: 400, height: 200, top: 8, …}
bottom:208
height:200
left:8
right:408
top:8
width:400
x:8
y:8
3.常见想得到某个元素具体的位置,一般采用鼠标距离减去dom元素距
离浏览器距离
4.'event'事件中隐藏的两个快捷方法'event.srcElement' -- 获取当前dom
5.'event'事件中隐藏的两个快捷方法'event.srcElement.getBoundingClientRect()' -- 获取当前dom位置信息
6.去除css 拖住样式'user-select: none;'
~~~
>[danger] ##### 绑定鼠标点击红条时候触发事件
~~~
1.想在鼠标点击的时候触发一个事件,可以得到红色条距离dom的位置
,后期根据这个位置去触发让左侧和右侧距离重新计算。
2.点击事件应该绑定在哪里,如果将点击事件绑定在红条区域,会出现
当鼠标移出红条的位置的时候,整个动态布局停止
'event.target.addEventListener('mousemove',this.handleMousemove)'
3.因此不能写成上面的写法,而是应该绑定在整个document上
'document.addEventListener('mousemove',this.handleMousemove)'
4.在点击事件上,增加的鼠标移动后重新计算的事件分开写方便维护
5.增加移入事件后,要对应的加入移除事件的后续操作,因此在data
中声明一个变量'canMove'用来记录当前点击还是未点击来触
发'handleMousemove' 事件
6.由于'mousemove' 是绑定在全局的,因此'mouseup' 也要绑定全局
7.按照第六条的思路需要在页面上绑定两个事件,但实际可以整合在
'@mousedown="handleMousedown" ' 中,这里面已经绑定了一个鼠标
移入事件,因此直接在绑定一个鼠标抬起事件
'handleMousup'
8.当我们计算'leftOffset'也就是左面区域的百分比的时候,鼠标到左侧边框
距离除以整体距离,就会出现一个效果,一瞬间区域发生变化并且鼠标
在整个红色区域,为了避免这个问题定位计算的时候直接从红条中心
位置计算,对应代码地方的g公式如下:
'const offset = event.pageX - this.initOffset + this.triggerWidth / 2 - outerRect.left;'
~~~
~~~
<template>
<div class="split-pane-wrapper" ref="outer">
<div class="pane pane-left" :style="{width:leftOffsetPercent}">
<button @click="handleClick">点击减少左侧宽度</button>
</div>
<div class="pane-trigger-con" @mousedown="handleMousedown" :style="{left:triggerLeft, width: `${triggerWidth}px`}"></div>
<div class="pane pane-right" :style="{left:leftOffsetPercent}"></div>
</div>
</template>
<script>
export default {
name: "split-pane",
props:{
triggerWidth:{
type:Number,
default:8,
},
},
data(){
return{
// 在这定义一个值。这样用户可以直接指定占比的值
// 在页面css 布局使用的值 使用计算属性拼接即可
leftOffset:0.3,
// 增一个判断 鼠标移入移除
canMove :false,
// 去除误差值
initOffset: 0
}
},
methods:{
handleClick(){
this.leftOffset -= 0.02
},
handleMousemove(event){
// pageX 是鼠标距离浏览器x轴的距离
// 想获取 红条在布局页面的准确位置需要用鼠标距离 - 容器对浏览器的距离
// 因此给 这个外部的div 绑上ref 属性用来获取,获取可以使用getBoundingClientRect()
//console.log(event.pageX -this.$refs.outer.getBoundingClientRect().left)
if(!this.canMove) return
const outerRect = this.$refs.outer.getBoundingClientRect();
// 整个leftOffset 的像素距离其实是其实就是pageX鼠标距离减去最外层整理距离浏览器的距离
//根据上面可以得到一个公式就是 event.pageX-outerRect.left
// 但要注意实际上中间区域是盖住了部分左右区域,也就是说只要我们进行了改变
// 就会自动到整个中间区域的中间位置,因此需要在点击的时候需要得到整中间区域最右面的位置
// 根据上面得到一个公式减去 this.initOffset也就是扣除无用区域获得中间区域最右面
// 为了去除因为中间区域覆盖的影响加上中间区域一半的宽度
const offset = event.pageX - this.initOffset + this.triggerWidth / 2 - outerRect.left;
this.leftOffset = (offset/outerRect.width)
},
handleMousup(){
// 抬起后 重新对canMove 赋值,让红条不能移动
this.canMove = false
},
handleMousedown(event){
//event.target.addEventListener('mousemove',this.handleMousemove)
document.addEventListener('mousemove',this.handleMousemove)
document.addEventListener('mouseup',this.handleMousup)
this.initOffset = event.pageX - event.srcElement.getBoundingClientRect().left
this.canMove = true;
},
},
computed:{
// 动态属性去拼接生成css 实际需要的代%形式的数据
leftOffsetPercent(){
return `${this.leftOffset * 100}%`
},
triggerLeft(){
return `calc(${this.leftOffset * 100}% - ${this.triggerWidth/2}px)`
},
},
}
</script>
<style lang="less">
.split-pane-wrapper{
width: 100%;
height: 100%;
position: relative;
.pane{
position: absolute;
height: 100%;
top:0;
&-left{
/*width: 30%;*/
background: brown;
}
&-right{
right: 0;
bottom: 0;
/*left: 30%;*/
background: chartreuse;
}
&-trigger-con{
z-index: 100;
height: 100%;
background: red;
position: absolute;
top: 0;
}
}
}
</style>
~~~
>[danger] ##### 最后的封装
~~~
1.上面已经可以实现整体使用,但是需要注意拖拽是有限度的,因此也需要设置拖拽的最大值最小值
2.上面的代码是我们指定'leftOffset'也是百分比占比,如果让组件去控制,就要有一个问题,
当我么拖拽的时候会重新改变这个值因此就是涉及到,父传子和子传父同时出发,想同时
出发两种方法在使用组件的时候使用v-model 和 使用语法糖sync
~~~
* 在使用组件的时候
~~~
<template>
<div class="split-pane-con">
<split-pane :value.sync="offset">
<div slot="left">left</div>
<div slot="right">right</div>
</split-pane>
</div>
</template>
<script>
import SplitPane from '_c/split-pane'
export default {
components: {
SplitPane
},
data () {
return {
offset: 0.8
}
},
methods: {
// handleInput (value) {
// this.offset = value
// }
}
}
</script>
<style lang="less">
.split-pane-con{
width: 400px;
height: 200px;
background: papayawhip;
}
</style>
~~~
* 组件封装代码
~~~
<template>
<div class="split-pane-wrapper" ref="outer">
<div class="pane pane-left" :style="{ width: leftOffsetPercent, paddingRight: `${this.triggerWidth / 2}px` }">
<slot name="left"></slot>
</div>
<div class="pane-trigger-con" @mousedown="handleMousedown" :style="{ left: triggerLeft, width: `${triggerWidth}px` }"></div>
<div class="pane pane-right" :style="{ left: leftOffsetPercent, paddingLeft: `${this.triggerWidth / 2}px` }">
<slot name="right"></slot>
</div>
</div>
</template>
<script>
export default {
name: 'SplitPane',
props: {
value: {
type: Number,
default: 0.5
},
triggerWidth: {
type: Number,
default: 8
},
min: {
type: Number,
default: 0.1
},
max: {
type: Number,
default: 0.9
}
},
data () {
return {
// leftOffset: 0.3,
canMove: false,
initOffset: 0
}
},
computed: {
leftOffsetPercent () {
return `${this.value * 100}%`
},
triggerLeft () {
return `calc(${this.value * 100}% - ${this.triggerWidth / 2}px)`
}
},
methods: {
handleClick () {
this.leftOffset -= 0.02
},
handleMousedown (event) {
document.addEventListener('mousemove', this.handleMousemove)
document.addEventListener('mouseup', this.handleMouseup)
this.initOffset = event.pageX - event.srcElement.getBoundingClientRect().left
this.canMove = true
},
handleMousemove (event) {
if (!this.canMove) return
const outerRect = this.$refs.outer.getBoundingClientRect()
let offsetPercent = (event.pageX - this.initOffset + this.triggerWidth / 2 - outerRect.left) / outerRect.width
if (offsetPercent < this.min) offsetPercent = this.min
if (offsetPercent > this.max) offsetPercent = this.max
// this.$emit('input', offsetPercent)
this.$emit('update:value', offsetPercent)
},
handleMouseup () {
this.canMove = false
}
}
}
</script>
<style lang="less">
.split-pane-wrapper{
height: 100%;
width: 100%;
position: relative;
.pane{
position: absolute;
top: 0;
height: 100%;
&-left{
// width: 30%;
background: palevioletred;
}
&-right{
right: 0;
bottom: 0;
background: paleturquoise;
}
&-trigger-con{
height: 100%;
background: red;
position: absolute;
top: 0;
z-index: 10;
user-select: none;
cursor: col-resize;
}
}
}
</style>
~~~
- Vue--基础篇章
- Vue -- 介绍
- Vue -- MVVM
- Vue -- 创建Vue实例
- Vue -- 模板语法
- Vue -- 指令用法
- v-cloak -- 遮盖
- v-bind -- 标签属性动态绑定
- v-on -- 绑定事件
- v-model -- 双向数据绑定
- v-for -- 只是循环没那么简单
- 小知识点 -- 计划内属性
- key -- 属性为什么要加
- 案例说明
- v-if/v-show -- 显示隐藏
- v-for 和 v-if 同时使用
- v-pre -- 不渲染大大胡语法
- v-once -- 只渲染一次
- Vue -- class和style绑定
- Vue -- filter 过滤器
- Vue--watch/computed/fun
- watch -- 巧妙利用watch思想
- Vue -- 自定义指令
- Vue -- $方法
- Vue--生命周期
- Vue -- 专属ajax
- Vue -- transition过渡动画
- 前面章节的案例
- 案例 -- 跑马灯效果
- 案例 -- 选项卡内容切换
- 案例-- 筛选商品
- 案例 -- 搜索/删除/更改
- 案例 -- 用computed做多选
- 案例 -- checked 多选
- Vue--组件篇章
- component -- 介绍
- component -- 使用全局组件
- component -- 使用局部组件
- component -- 组件深入
- component -- 组件传值父传子
- component -- 组件传值子传父
- component -- 子传父语法糖拆解
- component -- 父组件操作子组件
- component -- is 动态切换组件
- component -- 用v-if/v-show控制子组件
- component -- 组件切换的动画效果
- component -- slot 插槽
- component -- 插槽2.6
- component -- 组件的生命周期
- component -- 基础组件全局注册
- VueRouter--获取路由参数
- VueRouter -- 介绍路由
- VueRouter -- 安装
- VueRouter -- 使用
- VueRouter--router-link简单参数
- VueRouter--router-link样式问题
- VueRouter--router-view动画效果
- VueRouter -- 匹配优先级
- vueRouter -- 动态路由
- VueRouter -- 命名路由
- VueRouter -- 命名视图
- VueRouter--$router 获取函数
- VueRouter--$route获取参数
- VueRouter--路由嵌套
- VueRouter -- 导航守卫
- VueRouter -- 写在最后
- Vue--模块化方式结构
- webpack--自定义配置
- webpack -- 自定义Vue操作
- VueCli -- 3.0可视化配置
- VueCli -- 3.0 项目目录
- Vue -- 组件升级篇
- Vue -- 组件种类与组件组成
- Vue -- 组件prop、event、slot 技巧
- Vue -- 组件通信(一)
- Vue -- 组件通信(二)
- Vue -- 组件通信(三)
- Vue -- 组件通信(四)
- Vue -- 组件通信(五)
- Vue -- 组件通信(六)
- Vue -- bus非父子组件通信
- Vue -- 封装js插件成vue组件
- vue组件分装 -- 进阶篇
- Vue -- 组件封装splitpane(分割面板)
- UI -- 正式封装
- Vue -- iview 可编辑表格案例
- Ui -- iview 可以同时编辑多行
- Vue -- 了解递归组件
- UI -- 正式使用递归菜单
- Vue -- iview Tree组件
- Vue -- 利用通信仿写一个form验证
- Vue -- 使用自己的Form
- Vue -- Checkbox 组件
- Vue -- CheckboxGroup.vue
- Vue -- Alert 组件
- Vue -- 手动挂载组件
- Vue -- Alert开始封装
- Vue -- 动态表单组件
- Vue -- Vuex组件的状态管理
- Vuex -- 参数使用理解
- Vuex -- state扩展
- Vuex -- getters扩展
- Vuex--mutations扩展
- Vuex -- Action 异步
- Vuex -- plugins插件
- Vuex -- v-model写法
- Vuex -- 更多
- VueCli -- 技巧总结篇
- CLI -- 路由基础
- CLI -- 路由升级篇
- CLI --异步axios
- axios -- 封装axios
- CLI -- 登录写法
- CLI -- 权限
- CLI -- 简单权限
- CLI -- 动态路由加载
- CLI -- 数据性能优化
- ES6 -- 类的概念
- ES6类 -- 基础
- ES6 -- 继承
- ES6 -- 工作实战用类数据管理
- JS -- 适配器模式
- ES7 -- 装饰器(Decorator)
- 装饰器 -- 装饰器修饰类
- 装饰器--修饰类方法(知识扩展)
- 装饰器 -- 装饰器修饰类中的方法
- 装饰器 -- 执行顺序
- Reflect -- es6 自带版本
- Reflect -- reflect-metadata 版本
- 实战 -- 验证篇章(基础)
- 验证篇章 -- 搭建和目录
- 验证篇章 -- 创建基本模板
- 验证篇章 -- 使用
- 实战 -- 更新模型(为了迎合ui升级)
- 实战 -- 模型与接口对接
- TypeSprict -- 基础篇章
- TS-- 搭建(一)webpack版本
- TS -- 搭建(二)直接使用
- TS -- 基础类型
- TS -- 枚举类型
- TS -- Symbol
- TS -- interface 接口
- TS -- 函数
- TS -- 泛型
- TS -- 类
- TS -- 类型推论和兼容
- TS -- 高级类型(一)
- TS -- 高级类型(二)
- TS -- 关于模块解析
- TS -- 声明合并
- TS -- 混入
- Vue -- TS项目模拟
- TS -- vue和以前代码对比
- TS -- vue简单案例上手
- Vue -- 简单弄懂VueRouter过程
- VueRouter -- 实现简单Router
- Vue-- 原理2.x源码简单理解
- 了解 -- 简单的响应式工作原理
- 准备工作 -- 了解发布订阅和观察者模式
- 了解 -- 响应式工作原理(一)
- 了解 -- 响应式工作原理(二)
- 手写 -- 简单的vue数据响应(一)
- 手写 -- 简单的vue数据响应(二)
- 模板引擎可以做的
- 了解 -- 虚拟DOM
- 虚拟dom -- 使用Snabbdom
- 阅读 -- Snabbdom
- 分析snabbdom源码 -- h函数
- 分析snabbdom -- init 方法
- init 方法 -- patch方法分析(一)
- init 方法 -- patch方法分析(二)
- init方法 -- patch方法分析(三)
- 手写 -- 简单的虚拟dom渲染
- 函数表达解析 - h 和 create-element
- dom操作 -- patch.js
- Vue -- 完成一个minVue
- minVue -- 打包入口
- Vue -- new实例做了什么
- Vue -- $mount 模板编译阶段
- 模板编译 -- 分析入口
- 模板编译 -- 分析模板转译
- Vue -- mountComponent 挂载阶段
- 挂载阶段 -- vm._render()
- 挂载阶段 -- vnode
- 备份章节
- Vue -- Nuxt.js
- Vue3 -- 学习
- Vue3.x --基本功能快速预览
- Vue3.x -- createApp
- Vue3.x -- 生命周期
- Vue3.x -- 组件
- vue3.x -- 异步组件???
- vue3.x -- Teleport???
- vue3.x -- 动画章节 ??
- vue3.x -- 自定义指令 ???
- 深入响应性原理 ???
- vue3.x -- Option API VS Composition API
- Vue3.x -- 使用set up
- Vue3.x -- 响应性API
- 其他 Api 使用
- 计算属性和监听属性
- 生命周期
- 小的案例(一)
- 小的案例(二)-- 泛型
- Vue2.x => Vue3.x 导读
- v-for 中的 Ref 数组 -- 非兼容
- 异步组件
- attribute 强制行为 -- 非兼容
- $attrs 包括 class & style -- 非兼容
- $children -- 移除
- 自定义指令 -- 非兼容
- 自定义元素交互 -- 非兼容
- Data选项 -- 非兼容
- emits Option -- 新增
- 事件 API -- 非兼容
- 过滤器 -- 移除
- 片段 -- 新增
- 函数式组件 -- 非兼容
- 全局 API -- 非兼容
- 全局 API Treeshaking -- 非兼容
- 内联模板 Attribute -- 非兼容
- key attribute -- 非兼容
- 按键修饰符 -- 非兼容
- 移除 $listeners 和 v-on.native -- 非兼容
- 在 prop 的默认函数中访问 this -- ??
- 组件使用 v-model -- 非兼容
- 渲染函数 API -- ??
- Slot 统一 ??
- 过渡的 class 名更改 ???
- Transition Group 根元素 -- ??
- v-if 与 v-for 的优先级对比 -- 非兼容
- v-bind 合并行为 非兼容
- 监听数组 -- 非兼容