Vue之ResizableContainer
效果
代码
<template>
<div class="container">
<div class="resizable-container" v-panel-resize:vertical="handleResize">
<div class="item item-1 wrapper" weight="0.3">1div>
<div class="item item-2 wrapper" weight="0.35">2div>
<div class="item item-3 wrapper" weight="0.35">3div>
div>
div>
template>
<script>
import PanelResize from '@/directive/resizer'
export default {
directives: {
PanelResize
},
data: () => ({
}),
mounted() {
},
methods: {
handleResize(weights) {
console.log(weights)
}
}
};
</script>
<style lang="scss">
.resizable-container {
display: flex;
flex: 1;
flex-direction: column;
align-items: stretch;
min-width: 0;
min-height: 0;
height: 100%;
&.horizontal {
flex-direction: row;
}
.wrapper {
display: flex;
flex: 1;
flex-direction: column;
align-items: stretch;
overflow: hidden;
min-width: 20px;
min-height: 20px;
&.horizontal {
flex-direction: row;
}
}
}
.ide-divider {
position: absolute;
z-index: 97;
&:after {
position: absolute;
background-color: #505050;
content: "";
}
&.horizontal {
right: 0;
top: 0;
width: 7px;
height: 100%;
margin: 0 -3px;
cursor: ew-resize;
&:after {
top: 0;
bottom: 0;
left: 3px;
width: 1px;
}
}
&.vertical {
left: 0;
bottom: 0;
width: 100%;
height: 7px;
margin: -3px 0;
cursor: ns-resize;
&:after {
left: 0;
right: 0;
top: 3px;
height: 1px;
}
}
}
.container {
width: 400px;
height: 450px;
margin: 50px auto;
border: 2px solid rgb(18, 78, 243);
}
.item {
}
.item-1 {
background-color: pink;
}
.item-2 {
background-color: palegoldenrod;
}
.item-3 {
background-color: peachpuff;
}
style>
指令
import { hasClass } from "../../utils";
const resizer = {
bind(el, binding) {
binding.arg = binding.arg || 'vertical'
const handleResizer = typeof binding.value === 'function' ? binding.value : () => {}
const getChildren = (el) => {
let childs = el.childNodes || []
let children = []
childs.forEach(child => {
if (child.nodeType === 1 && !hasClass(child, 'ide-divider') && child.hasAttribute('weight')) {
children.push(child)
}
})
return children
}
const createDivider = (el, binding) => {
el.style.position = 'relative'
let divider = document.createElement('div')
divider.setAttribute('class', 'ide-divider ' + binding.arg)
el.appendChild(divider)
return divider
}
const handleResize = (targetElement, clientX, clientY, index, nextIndex, weights) => {
const horizontal = binding.arg === 'horizontal' ? true : false;
const { left, top } = targetElement.getBoundingClientRect();
const { offsetWidth, offsetHeight } = targetElement;
const position = horizontal ? clientX - left : clientY - top;
const containerSize = horizontal ? offsetWidth : offsetHeight;
let totalWeight = 0;
let subtotalWeight = 0;
weights.forEach((weight, i) => {
totalWeight += weight;
if (i < nextIndex) subtotalWeight += weight;
});
const newWeight = (position / containerSize) * totalWeight;
let deltaWeight = newWeight - subtotalWeight;
deltaWeight = Math.max(deltaWeight, -weights[index]);
deltaWeight = Math.min(deltaWeight, weights[nextIndex]);
weights[index] += deltaWeight;
weights[nextIndex] -= deltaWeight;
return weights
}
let target
const handleMouseDown = (e) => {
target = e.target
e.preventDefault()
document.addEventListener('mousemove', handleMouseMove, false)
document.addEventListener('mouseup', handleMouseUp, false)
}
const handleMouseMove = (e) => {
let dx = e.clientX
let dy = e.clientY
let children = getChildren(el)
let weights = []
let index = 0, nextIndex
children.forEach((child, i) => {
let weight = +(child.getAttribute('weight') || (1 / children.length))
weights.push(weight)
if (target && target.parentElement === child) {
index = i
nextIndex = i + 1
}
})
let newWeights = handleResize(el, dx, dy, index, nextIndex, weights)
children.forEach((child, i) => {
let weight = newWeights[i]
child.style.flexGrow = weight
child.setAttribute('weight', weight)
})
handleResizer(weights)
}
const handleMouseUp = () => {
target = null
document.removeEventListener('mousemove', handleMouseMove)
document.removeEventListener('mouseup', handleMouseUp)
}
const init = () => {
if (!el) {
return
}
const children = getChildren(el)
children.forEach((child, index) => {
let weight = +(child.getAttribute('weight') || (1 / children.length))
child.style.flexGrow = weight
if (index < children.length - 1) {
let divider = createDivider(child, binding)
divider.addEventListener('mousedown', handleMouseDown, false)
}
})
}
init()
}
}
resizer.install = Vue => Vue.directive('panel-resize', resizer)
export default resizer