Github portal: github.com/johnsonhoo0…
Implementation effect
Recently, I was fascinated by the development of front-end visualization and recently learned vue3, so I suddenly wanted to try to package a simple container zooming and zooming component in the way of VUE3. First look at the results:
idea
Just like on a piece of paper, we call it container; Inside the Container, wrap the element you want to zoom in and out with an object that allows it to zoom in and out, as shown in the GIF above. According to the syntax of Vue, what I want to achieve is something like this:
<div class="container" ref="parentRef">
<resize-able :parentRef="parentRef">
<button>button</button>
</resize-able>
</div>
Copy the code
The resize-able in the middle is the component we need to encapsulate. The outer container can be replaced with a desired container, such as a loose-leaf editor container.
Code section
foreplay
Before encapsulating the component, you implement a small demo that allows the current element to zoom in and out.
style
As can be seen from the figure, the main style is to click on the target element and generate a blue border and corner circle. Drag these circles to make the target element zoom in and out at different angles. It’s easy to imagine that these circles are always between the corners and the edges, and you can think about positioning them. (The location of the key ball is roughly 49%.) The code is as follows:
<div class="box">
<div class="box-ball"
v-for="item in 8"
:data-i="item"
:key="item"
@mousedown="handleResize"></div>
</div>
<style scoped>
.draggable-container {
width: 500px;
height: 500px;
border: 1px solid #aaa;
position: relative;
box-sizing: border-box;
}
.target-box {
display: block;
width: 300px;
height: 100px;
border: 1px solid #aaa;
background: #eee;
box-sizing: border-box;
position: absolute;
left: 100px;
top: 20px;
}
.target-can-move {
cursor: all-scroll;
}
.box-click {
border: 1px solid blue;
}
.box-ball {
width: 8px;
height: 8px;
border: 1px solid blue;
box-sizing: border-box;
border-radius: 50%;
position: absolute;
}
.box-ball:nth-child(1) {
top: -8px;
left: -8px;
cursor: nw-resize;
}
.box-ball:nth-child(2) {
top: -8px;
left: calc(50% - 4px);
cursor: n-resize;
}
.box-ball:nth-child(3) {
top: -8px;
right: -8px;
cursor: nesw-resize;
}
.box-ball:nth-child(4) {
top: calc(50% - 4px);
left: -8px;
cursor: w-resize;
}
.box-ball:nth-child(5) {
top: calc(50% - 4px);
right: -8px;
cursor: w-resize;
}
.box-ball:nth-child(6) {
left: -8px;
bottom: -8px;
cursor: nesw-resize;
}
.box-ball:nth-child(7) {
left: calc(50% - 4px);
bottom: -8px;
cursor: n-resize;
}
.box-ball:nth-child(8) {
right: -8px;
bottom: -8px;
cursor: nw-resize;
}
</style>
Copy the code
Mouse events
Then there’s the mouse. Onclick, onMouseDown, onMousemove, onMouseup
- [Fixed] Activate the target element when clicked (blue border and small circle)
- When the mouse is pressed down and in the active state, whether the event target is the ball or the element itself is clicked. If the ball is a ball, zoom in or out according to the direction of mouse movement of different balls. If it is the element itself, drag the target element.
- When the mouse is released, it means it is not moving or zooming in or out elements.
- When you click on the empty space of the outermost Container, it becomes inactive.
<template> <div class="draggable-container" @click="boxActive($event)" @mousedown="handleDown" @mousemove="handleMove" @mouseup="handleUp"> <div ref="targetBox" class="target-box" data-aim="canMove" :class="boxSelected ? 'box-click target-can-move' : ''" > <slot></slot> <div :class="boxSelected ? 'box-ball' : ''" v-for="item in 8" :data-i="item" :key="item"></div> </div> </div> </template> <script> import { ref } from 'vue' Const boxElement = Reactive ({containerWidth: 500, containerHeigth: reactive({containerWidth: 500, containerHeigth: 500, width: 300, height: 100, left: 100, top: 20}) const boxSelected = ref(false) const boxActive = function(e) {if (e.targe.className! Value = true} else {boxselection. value = false}} // Mouse-down event const handleDown = Function (e) {if (boxselection. value) {ballidx.value = e.target.dataset Ballidx.value <= 8) {mouseresizeDown. value = true return} if (e.target.dataset. Aim) { Mousemove. value = true return}}} const handleUp = function(e) {mouseresizeDown. value = false Mousemove. value = false} const handleMove = function(e) {if (mouseresizeDown. value) {// Const handleMove = function(e) {if (mouseresizeDown. value) {// Const handleMove = function(e) {if (mouseresizeDown. value) {// Contrast = e. arget. ClassName / / need to block the ball wide high bugs if (e.o ffsetX < = 8 | | e.o ffsetY < = 8) {return} the if (ballIdx. Value) { }} else if (mousemove.value) {move(e.movementx, contrast) {move(e.movementx, }}}} </script>Copy the code
Zooming in and out and moving elements
Here mainly describes how to achieve the zooming in and out of the target element and element movement. Of the abovehandleResize
andmove
Methods. The main thing to move is to not move elements out of the container, which is easy. The mouse cursor is always on the target element while moving, so the mouse cursor movesmovementX
andmovementY
It’s how far it moved, and we’re going to change thatleft
andtop
Value can change the current element position.
For zooming in and out of target elements, note:Take the small circle in the lower right corner as an example: When the mouse is dragging the element to zoom in and out, there may be three different situations when the cursor moves. It is necessary to judge the direction of mouse movement according to the situation:
A. If the mouse cursor is within the target element like 1, the element shrinks and the width isoffsetX
Height isoffsetY
;
B. If the cursor is in a small circle such as 2, it does not move because the offset of the small circle is very small and can be ignored.
C. If the cursor is over the container like 3, the element is enlarged and the width isoffsetX - left
Height isoffsetY - top
Function move(movementX, MovementY) {/ / don't let the target element to move to the outside of the container if (boxElement. Width + boxElement. Left + movementX > boxElement. ContainerWidth) {movementX = 0 } if (boxElement.height + boxElement.top + movementY > boxElement.containerHeigth) { movementY = 0 } boxElement.left += movementX boxElement.top += movementY boxElement.left = boxElement.left < 0 ? 0 : boxElement.left boxElement.top = boxElement.top < 0 ? Zero: Boxelement.top // Update the position of the target element updateStyle()} Function handleResize(contrast, currentBall, offsetX, offsetY) { if (currentBall === '8') { if (contrast === 'draggable-container') { boxElement.width = offsetX - boxElement.left boxElement.height = offsetY - boxElement.top } else { boxElement.width = offsetX boxElement.height = offsetY } } else if (currentBall === '7') { if (contrast === 'draggable-container') { boxElement.height = offsetY - boxElement.top } else { boxElement.height = offsetY } } else if (currentBall === '6') { if (contrast === 'draggable-container') { boxElement.height = offsetY - boxElement.top const gapX = boxElement.left - offsetX boxElement.width = gapX + boxElement.width boxElement.left = boxElement.left - gapX } else { boxElement.width = boxElement.width - offsetX boxElement.left = boxElement.left + offsetX } } else if (currentBall === '5') { if (contrast === 'draggable-container') { boxElement.width = offsetX - boxElement.left } else { boxElement.width = offsetX } } else if (currentBall === '4') { if (contrast === 'draggable-container') { const gapX = boxElement.left - offsetX boxElement.width = gapX + boxElement.width boxElement.left = boxElement.left - gapX } else { boxElement.width = boxElement.width - offsetX boxElement.left = boxElement.left + offsetX } } else if (currentBall === '3') { if (contrast === 'draggable-container') { boxElement.width = offsetX - boxElement.left const gapY = boxElement.top - offsetY boxElement.height = gapY + boxElement.height boxElement.top = boxElement.top - gapY } else { boxElement.width = offsetX boxElement.height = boxElement.height - offsetY boxElement.top = boxElement.top + offsetY } } else if (currentBall === '2') { if (contrast === 'draggable-container') { boxElement.height = boxElement.top - offsetY + boxElement.height boxElement.top = offsetY } else { boxElement.height = boxElement.height - offsetY boxElement.top = boxElement.top + offsetY } } else if (currentBall === '1') { if (contrast === 'draggable-container') { const gapX = boxElement.left - offsetX boxElement.width = gapX + boxElement.width boxElement.left = boxElement.left - gapX boxElement.height = boxElement.height + boxElement.top - offsetY boxElement.top = offsetY } else { boxElement.width = boxElement.width - offsetX boxElement.left = boxElement.left + offsetX boxElement.height = boxElement.height - offsetY boxElement.top = Boxelement.top + offsetY}} updateStyle()}Copy the code
The final effect is:
Package as a component
On the basis of the above, to achieve
<div class="container" ref="parentRef">
<resize-able :parentRef="parentRef">
<button>button</button>
</resize-able>
</div>
Copy the code
I need to automatically bind events to outer elements. There are two key points:
<div class="draggable-container"
@click="boxActive($event)"
@mousedown="handleDown"
@mousemove="handleMove"
@mouseup="handleUp">
Copy the code
- We can use the REF of vue to get dom node and bind events to outer container after getting container node.
- Can be achieved by
window.getComputedStyle(dom, null)
Method to get the height, width and position of the target element, which is stored in the initialization data.
<div ref="targetBox" class="target-box" data-aim="canMove" <div ref=" target-box" data-aim="canMove" :class="boxSelected ? `box-click target-can-move` : ``" > <div :class="boxSelected ? 'box-ball' : ''" v-for="item in 8" :data-i="item" :key="item" ></div> <div v-if="boxSelected" class="box-details"> <div>x: {{ boxElement.left }}px</div> <div>y: {{ boxElement.top }}px</div> <div>width: {{ boxElement.width }}px</div> <div>height: {{ boxElement.height }}px</div> </div> </div> </template> <script> import { ref, reactive, onMounted, getCurrentInstance, toRaw } from "vue"; export default { const slot = ref(null) ... const slotStyle = window.getComputedStyle(slot.value, null) boxElement.width = parseInt(slotStyle.width) boxElement.height = parseInt(slotStyle.height) boxElement.left = parseInt(slotStyle.left) boxElement.top = parseInt(slotStyle.top) ... / / add mouse events to the outer container function bindMouseEvent (container) {/ / support modern browsers containerSize. ContainerWidth = parseInt(window.getComputedStyle(container, null).width) containerSize.containerHeigth = parseInt(window.getComputedStyle(container, null).height) container.addEventListener('click', boxActive) container.addEventListener('mousedown', handleDown) container.addEventListener('mousemove', handleMove) container.addEventListener('mouseup', handleUp) }Copy the code
And you’re done!