可拖拽更改盒模型大小
CSS的resize
属性已经满足大部分使用情况。当需要更好的交互时(比如可拖拽盒模型左右两边的边框实现调整盒模型的宽度、拖拽盒模型上下两条边框实现调整盒模型的高度),则需要重新对样式进行调整。
源代码
vue
<template>
<div class="resize-box" :class="[props.resize]">
<template v-if="leftBar">
<div class="resize-bar-left" ref="resizeBarLeftEl"></div>
<div class="dividing-line-left"></div>
</template>
<template v-if="rightBar">
<div class="resize-bar-right" ref="resizeBarRightEl"></div>
<div class="dividing-line-right"></div>
</template>
<template v-if="topBar">
<div class="resize-bar-top" ref="resizeBarTopEl"></div>
<div class="dividing-line-top"></div>
</template>
<template v-if="bottomBar">
<div class="resize-bar-bottom" ref="resizeBarBottomEl"></div>
<div class="dividing-line-bottom"></div>
</template>
<div class="real-content" ref="contentRef">
<slot></slot>
</div>
</div>
</template>
<script setup>
import { ref, watch, computed, onUnmounted, onMounted } from 'vue';
const props = defineProps({
// 同 CSS resize
resize: {
type: String,
default: 'both',
},
bar: {
type: Array,
default: () => ['bottom', 'right'],
},
});
const leftBar = computed(() => {
return (
(props.resize === 'horizontal' || props.resize === 'both') &&
props.bar.includes('left')
);
});
const rightBar = computed(() => {
return (
(props.resize === 'horizontal' || props.resize === 'both') &&
props.bar.includes('right')
);
});
const topBar = computed(() => {
return (
(props.resize === 'vertical' || props.resize === 'both') &&
props.bar.includes('top')
);
});
const bottomBar = computed(() => {
return (
(props.resize === 'vertical' || props.resize === 'both') &&
props.bar.includes('bottom')
);
});
const bindObserve = (el, updateBoxSize) => {
const MutationObserver =
window.MutationObserver ||
window.WebKitMutationObserver ||
window.MozMutationObserver;
const resizeBarObserver = new MutationObserver((mutationList) => {
const width = getComputedStyle(el).getPropertyValue('width');
const height = getComputedStyle(el).getPropertyValue('height');
updateBoxSize(width, height);
});
resizeBarObserver.observe(el, {
attributes: true,
attributeFilter: ['style'],
attributeOldValue: true,
});
return resizeBarObserver;
};
const clearObserve = (observe) => {
if (observe) {
observe.disconnect();
observe.takeRecords();
}
};
const resizeBarLeftEl = ref();
const resizeBarRightEl = ref();
const resizeBarTopEl = ref();
const resizeBarBottomEl = ref();
const contentRef = ref();
const updateBoxWidth = (width, height) => {
contentRef.value.style.width = width;
if (resizeBarLeftEl.value) {
resizeBarLeftEl.value.style.width = width;
}
if (resizeBarRightEl.value.style) {
resizeBarRightEl.value.style.width = width;
}
};
const updateBoxHeight = (width, height) => {
contentRef.value.style.height = height;
if (resizeBarTopEl.value) {
resizeBarTopEl.value.style.height = height;
}
if (resizeBarBottomEl.value) {
resizeBarBottomEl.value.style.height = height;
}
};
const resizeWidthLeftObserve = ref(null);
const resizeWidthRightObserve = ref(null);
const resizeHeightTopObserve = ref(null);
const resizeHeightBottomObserve = ref(null);
onMounted(() => {
watch(
props,
() => {
clearObserve(resizeWidthLeftObserve.value);
clearObserve(resizeWidthRightObserve.value);
clearObserve(resizeHeightTopObserve.value);
clearObserve(resizeHeightBottomObserve.value);
resizeWidthLeftObserve.value = null;
resizeWidthRightObserve.value = null;
resizeHeightTopObserve.value = null;
resizeHeightBottomObserve.value = null;
if (leftBar.value) {
resizeWidthLeftObserve.value = bindObserve(
resizeBarLeftEl.value,
updateBoxWidth
);
}
if (rightBar.value) {
resizeWidthRightObserve.value = bindObserve(
resizeBarRightEl.value,
updateBoxWidth
);
}
if (topBar.value) {
resizeHeightTopObserve.value = bindObserve(
resizeBarTopEl.value,
updateBoxHeight
);
}
if (bottomBar.value) {
resizeHeightBottomObserve.value = bindObserve(
resizeBarBottomEl.value,
updateBoxHeight
);
}
},
{
immediate: true,
}
);
onUnmounted(() => {
clearObserve(resizeWidthLeftObserve.value);
clearObserve(resizeWidthRightObserve.value);
clearObserve(resizeHeightTopObserve.value);
clearObserve(resizeHeightBottomObserve.value);
});
});
</script>
<style lang="less" scoped>
@resizeBarWidth: 8px;
.resize-box {
display: inline-block;
position: relative;
border: 1px solid #666;
}
.resize-bar-left,
.resize-bar-right,
.resize-bar-top,
.resize-bar-bottom {
width: 100%;
height: 100%;
overflow: scroll;
position: absolute;
opacity: 0;
z-index: 1;
}
.resize-bar-left,
.resize-bar-right {
top: 0;
resize: horizontal;
&::-webkit-scrollbar {
width: @resizeBarWidth;
height: 99999px;
opacity: 0;
}
}
.resize-bar-top,
.resize-bar-bottom {
left: 0;
resize: vertical;
&::-webkit-scrollbar {
width: 99999px;
height: @resizeBarWidth;
opacity: 0;
}
}
.resize-bar-left {
transform-origin: 50% 50%;
transform: rotate(180deg);
left: -@resizeBarWidth;
&:hover,
&:active {
& ~ .dividing-line-left {
border-left: 1px dashed skyblue;
}
}
}
.resize-bar-right {
left: @resizeBarWidth;
&:hover,
&:active {
& ~ .dividing-line-right {
border-right: 1px dashed skyblue;
}
}
}
.resize-bar-top {
transform-origin: 50% 50%;
transform: rotate(180deg);
top: -@resizeBarWidth;
&:hover,
&:active {
& ~ .dividing-line-top {
border-top: 1px dashed skyblue;
}
}
}
.resize-bar-bottom {
top: @resizeBarWidth;
&:hover,
&:active {
& ~ .dividing-line-bottom {
border-bottom: 1px dashed skyblue;
}
}
}
.dividing-line-left,
.dividing-line-right,
.dividing-line-top,
.dividing-line-bottom {
position: absolute;
z-index: 5;
pointer-events: none;
}
.dividing-line-left {
top: 0;
left: 0;
width: 0;
height: 100%;
border-left: 1px solid transparent;
}
.dividing-line-right {
top: 0;
right: 0;
width: 0;
height: 100%;
border-right: 1px solid transparent;
}
.dividing-line-top {
top: 0;
left: 0;
height: 0;
width: 100%;
border-top: 1px solid transparent;
}
.dividing-line-bottom {
bottom: 0;
left: 0;
height: 0;
width: 100%;
border-bottom: 1px solid transparent;
}
.real-content {
display: inline-block;
position: relative;
z-index: 6;
overflow: hidden;
}
</style>