7 changed files with 630 additions and 1231 deletions
File diff suppressed because it is too large
@ -1,200 +0,0 @@ |
|||
<template> |
|||
<div class="canvas-bg" :style="bgStyle"> |
|||
<div :style="canvasStyle" class="canvas-panel-wrap"> |
|||
{{ canvasStyle }} <slot></slot> |
|||
</div> |
|||
|
|||
<canvas ref="topRulerRef" class="canvas-ruler top" :style="topStyle"></canvas> |
|||
<canvas ref="leftRulerRef" class="canvas-ruler left" :style="leftStyle"></canvas> |
|||
<i class="canvas-ruler-cross"></i> |
|||
|
|||
<div |
|||
class="canvas-move" |
|||
v-show="isMoveCanvas" |
|||
@mousedown="onMoveCanvasDown" |
|||
@mouseup="onMoveCanvasUp" |
|||
></div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script lang="ts"> |
|||
import { |
|||
defineComponent, |
|||
ref, |
|||
onMounted, |
|||
computed, |
|||
watch, |
|||
nextTick, |
|||
onBeforeUnmount, |
|||
} from "vue"; |
|||
import { drawRuler } from "./drawRuler.ts"; |
|||
import { useUserRulerStore } from "@/store/ruler/index"; |
|||
import { keyCode } from "./data/keyCode"; |
|||
|
|||
export default defineComponent({ |
|||
name: "CanvasBg", |
|||
|
|||
setup() { |
|||
const editorStore = useUserRulerStore(); |
|||
const topRulerRef = ref(); |
|||
const leftRulerRef = ref(); |
|||
const isMoveCanvas = computed(() => editorStore.isMoveCanvas); |
|||
watch( |
|||
() => editorStore.screenWidth, |
|||
() => { |
|||
refreshRuler(); |
|||
} |
|||
); |
|||
watch( |
|||
() => editorStore.screenHeight, |
|||
() => { |
|||
refreshRuler(); |
|||
} |
|||
); |
|||
watch( |
|||
() => editorStore.scale, |
|||
() => { |
|||
refreshRuler(); |
|||
} |
|||
); |
|||
|
|||
let isRuler = false; |
|||
const refreshRuler = () => { |
|||
if (!isRuler) { |
|||
isRuler = true; |
|||
nextTick(() => { |
|||
drawRuler( |
|||
"top", |
|||
topRulerRef.value, |
|||
editorStore.scale, |
|||
editorStore.showWidth, |
|||
24, |
|||
60 |
|||
); |
|||
drawRuler( |
|||
"left", |
|||
leftRulerRef.value, |
|||
editorStore.scale, |
|||
editorStore.showWidth, |
|||
24, |
|||
60 |
|||
); |
|||
isRuler = false; |
|||
}); |
|||
} |
|||
}; |
|||
const scrollLeft = computed(() => { |
|||
return editorStore.scrollLeft - 24; |
|||
}); |
|||
const scrollTop = computed(() => { |
|||
return editorStore.scrollTop - 24; |
|||
}); |
|||
const onKeyAction = (e: KeyboardEvent) => { |
|||
if (e.keyCode == keyCode.space) { |
|||
editorStore.setMoveCanvas(!editorStore.isMoveCanvas); |
|||
} |
|||
}; |
|||
const onWheelAction = (e: WheelEvent) => { |
|||
if (isMoveCanvas.value) { |
|||
if (e.wheelDelta > 0) { |
|||
editorStore.setScale(editorStore.scale + 1); |
|||
} else { |
|||
editorStore.setScale(editorStore.scale - 1); |
|||
} |
|||
} |
|||
}; |
|||
onMounted(() => { |
|||
window.addEventListener("keydown", onKeyAction); |
|||
window.addEventListener("wheel", onWheelAction); |
|||
refreshRuler(); |
|||
}); |
|||
onBeforeUnmount(() => { |
|||
window.removeEventListener("keydown", onKeyAction); |
|||
window.removeEventListener("wheel", onWheelAction); |
|||
}); |
|||
let moveInfo = { |
|||
startX: 0, |
|||
startY: 0, |
|||
}; |
|||
return { |
|||
topRulerRef, |
|||
leftRulerRef, |
|||
topStyle: computed(() => ({ |
|||
left: -scrollLeft.value + "px", |
|||
})), |
|||
leftStyle: computed(() => ({ top: -scrollTop.value + "px" })), |
|||
isMoveCanvas, |
|||
bgStyle: computed(() => ({ |
|||
width: editorStore.showWidth + "px", |
|||
height: editorStore.showHeight + "px", |
|||
})), |
|||
canvasStyle: computed(() => ({ |
|||
left: -scrollLeft.value + "px", |
|||
top: -scrollTop.value + "px", |
|||
width: editorStore.screenWidth + "px", |
|||
height: editorStore.screenHeight + "px", |
|||
transform: `scale(${editorStore.scale * 0.01})`, |
|||
})), |
|||
scrollLeft, |
|||
scrollTop, |
|||
onMoveCanvasDown: (e: MouseEvent) => { |
|||
e.stopPropagation(); |
|||
|
|||
moveInfo = { |
|||
startX: e.clientX, |
|||
startY: e.clientY, |
|||
}; |
|||
}, |
|||
onMoveCanvasUp: (e: MouseEvent) => { |
|||
e.stopPropagation(); |
|||
let left = scrollLeft.value - (e.clientX - moveInfo.startX); |
|||
let top = scrollTop.value - (e.clientY - moveInfo.startY); |
|||
// console.log('move', left, top); |
|||
editorStore.setScroll({ left, top }); |
|||
}, |
|||
}; |
|||
}, |
|||
}); |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.canvas-ruler-cross { |
|||
display: inline-block; |
|||
width: 24px; |
|||
height: 24px; |
|||
position: absolute; |
|||
|
|||
background-color: var(--ruler-bg); |
|||
} |
|||
.canvas-panel-wrap { |
|||
position: absolute; |
|||
box-shadow: var(--canvas-shadow) 0 0 30px 0; |
|||
transform-origin: left top; |
|||
margin-left: 60px; |
|||
margin-top: 60px; |
|||
} |
|||
.canvas-move { |
|||
position: absolute; |
|||
height: 100%; |
|||
width: 100%; |
|||
background-color: var(--move-bg); |
|||
cursor: move; |
|||
} |
|||
|
|||
.canvas-ruler { |
|||
position: absolute; |
|||
transform-origin: left top; |
|||
// cursor: ew-resize; |
|||
background-color: var(--ruler-bg); |
|||
|
|||
&.left { |
|||
top: 24px; |
|||
left: 0px; |
|||
} |
|||
|
|||
&.top { |
|||
top: 0px; |
|||
left: 24px; |
|||
} |
|||
} |
|||
</style> |
|||
@ -1,148 +0,0 @@ |
|||
<template> |
|||
<div class="canvas-slider"> |
|||
<el-tooltip placement="top" effect="light"> |
|||
<template #content> |
|||
<div class="operation-tip-item"> |
|||
<div>按下空格键,可移动画布</div> |
|||
<div>按下空格键,滚动鼠标滑轮可放缩画布</div> |
|||
</div> |
|||
</template> |
|||
<i class="el-icon-thumb" /> |
|||
</el-tooltip> |
|||
<el-input-number v-model="scalePercent" :min="20" :max="200" size="small"></el-input-number> |
|||
<el-slider |
|||
size="small" |
|||
v-model="scalePercent" |
|||
:min="20" |
|||
:max="200" |
|||
:step="5" |
|||
style="width: 100px; margin: 0 12px" |
|||
></el-slider> |
|||
|
|||
<el-tooltip placement="top"> |
|||
<template #content> 实际大小 </template> |
|||
<i class="el-icon-full-screen" @click="onRealCanvas" /> |
|||
</el-tooltip> |
|||
<el-tooltip placement="top"> |
|||
<template #content> 适应屏幕大小 </template> |
|||
<i class="el-icon-aim" @click="onFitCanvas" /> |
|||
</el-tooltip> |
|||
<i |
|||
class="el-icon-monitor" |
|||
:style="{ color: showThumbnail ? 'dodgerblue' : 'rgba(0,0,0,0.5)' }" |
|||
@click="onMagic" |
|||
></i> |
|||
</div> |
|||
</template> |
|||
|
|||
<script lang="ts"> |
|||
import { useEditorStore } from '@/stores/editor'; |
|||
|
|||
import { defineComponent, ref, computed, onMounted, onBeforeUnmount } from 'vue'; |
|||
|
|||
//下方状态栏 |
|||
export default defineComponent({ |
|||
name: 'CanvasSlider', |
|||
setup() { |
|||
const store = useEditorStore(); |
|||
|
|||
const scalePercent = computed({ |
|||
get: () => store.scale, |
|||
set: (value) => { |
|||
store.setScale(value); |
|||
} |
|||
}); |
|||
const showThumbnail = computed({ |
|||
get: () => store.$state.isThumbnail, |
|||
set: (value) => { |
|||
store.setThumbnail(value); |
|||
} |
|||
}); |
|||
|
|||
const onFitCanvas = () => { |
|||
store.setScale( |
|||
parseInt( |
|||
//@ts-ignore |
|||
((document.getElementById('dashboard').offsetHeight - 84) / store.screenHeight) * 100 |
|||
) |
|||
); |
|||
}; |
|||
const onRealCanvas = () => { |
|||
store.setScale(100); |
|||
}; |
|||
const onMagic = () => { |
|||
showThumbnail.value = !showThumbnail.value; |
|||
}; |
|||
|
|||
return { |
|||
showThumbnail, |
|||
|
|||
scalePercent, |
|||
|
|||
//method |
|||
onMagic, |
|||
onFitCanvas, |
|||
onRealCanvas |
|||
}; |
|||
} |
|||
}); |
|||
</script> |
|||
<style lang="scss" scoped> |
|||
.operation-tip-item > div { |
|||
color: var(--el-color-primary); |
|||
background: rgba(26, 103, 255, 0.1); |
|||
padding: 5px 10px; |
|||
} |
|||
|
|||
.operation-tip-item > div:not(:last-child) { |
|||
margin-bottom: 8px; |
|||
} |
|||
|
|||
i { |
|||
font-size: 16px; |
|||
color: var(--slider-icon); |
|||
cursor: pointer; |
|||
|
|||
&:not(:last-child) { |
|||
margin-right: 10px; |
|||
} |
|||
} |
|||
|
|||
:deep(.el-input-number__decrease) { |
|||
border: none; |
|||
background: none; |
|||
} |
|||
|
|||
:deep(.el-input-number__increase) { |
|||
border: none; |
|||
background: none; |
|||
} |
|||
|
|||
:deep(.el-input__wrapper) { |
|||
border: none; |
|||
border-radius: 0; |
|||
background: none; |
|||
box-shadow: none; |
|||
} |
|||
|
|||
.el-input-number--small { |
|||
width: 84px; |
|||
} |
|||
|
|||
$line: solid 1px var(--canvas-slider-border); |
|||
|
|||
.canvas-slider { |
|||
display: flex; |
|||
position: fixed; |
|||
bottom: 10px; |
|||
right: 310px; |
|||
background-color: var(--slider-bg); |
|||
height: 32px; |
|||
justify-content: flex-end; |
|||
align-items: center; |
|||
border: $line; |
|||
z-index: 9; |
|||
border-radius: 2px; |
|||
padding: 0 16px; |
|||
} |
|||
</style> |
|||
@ -1,193 +0,0 @@ |
|||
<template> |
|||
<canvas |
|||
class="canvas-thumbnail" |
|||
:width="canvasConfig.thumbnailWrapWidth" |
|||
:height="canvasConfig.thumbnailWrapHeight" |
|||
ref="thumbnailRef" |
|||
v-show="showThumbnail" |
|||
@mousedown="onMoveStart" |
|||
@mousemove="onMove" |
|||
@mouseup="onMoveEnd" |
|||
@mouseleave="onMoveEnd" |
|||
> |
|||
</canvas> |
|||
</template> |
|||
|
|||
<script lang="ts"> |
|||
import { useEditorStore } from '@/stores/editor'; |
|||
import { |
|||
defineComponent, |
|||
ref, |
|||
computed, |
|||
reactive, |
|||
watch, |
|||
onMounted, |
|||
nextTick, |
|||
onBeforeUnmount |
|||
} from 'vue'; |
|||
export default defineComponent({ |
|||
name: 'CanvasThumbnail', |
|||
setup(props, context) { |
|||
const editorStore = useEditorStore(); |
|||
const thumbnailRef = ref(); |
|||
const canvasConfig = computed(() => editorStore.canvasConfig); |
|||
let isLock = false; |
|||
const viewBox = computed({ |
|||
get: () => ({ viewWidth: editorStore.viewWidth, viewHeight: editorStore.viewHeight }), |
|||
set: (v) => { |
|||
editorStore.setViewBox(v); |
|||
} |
|||
}); |
|||
const unscale = computed(() => { |
|||
if (editorStore.scale > 100) { |
|||
return 1 / editorStore.thumbnailSize; |
|||
} else { |
|||
return 10; |
|||
} |
|||
}); |
|||
|
|||
const getViewBox = () => { |
|||
nextTick(() => { |
|||
let dashboardDom = document.getElementById('dashboard'); |
|||
if (!dashboardDom) { |
|||
return; |
|||
} |
|||
viewBox.value.viewWidth = dashboardDom.offsetWidth; |
|||
viewBox.value.viewHeight = dashboardDom.offsetHeight; |
|||
drawThumbnail(); |
|||
}); |
|||
}; |
|||
const drawThumbnail = () => { |
|||
if (!isLock) { |
|||
isLock = true; |
|||
nextTick(() => { |
|||
let startLen = 6; |
|||
|
|||
const ctx = thumbnailRef.value.getContext('2d'); |
|||
ctx.clearRect( |
|||
0, |
|||
0, |
|||
canvasConfig.value.thumbnailWrapWidth, |
|||
canvasConfig.value.thumbnailWrapHeight |
|||
); |
|||
ctx.beginPath(); |
|||
ctx.fillStyle = 'rgba(26, 103, 255, 0.5)'; |
|||
ctx.rect( |
|||
startLen, |
|||
startLen, |
|||
canvasConfig.value.thumbnailWidth, |
|||
canvasConfig.value.thumbnailHeight |
|||
); |
|||
ctx.fill(); |
|||
ctx.beginPath(); |
|||
ctx.strokeStyle = '#1a67ff'; |
|||
ctx.rect( |
|||
Math.round(editorStore.scrollLeft * editorStore.thumbnailSize), |
|||
Math.round(editorStore.scrollTop * editorStore.thumbnailSize), |
|||
viewBox.value.viewWidth * editorStore.thumbnailSize, |
|||
viewBox.value.viewHeight * editorStore.thumbnailSize |
|||
); |
|||
ctx.stroke(); |
|||
|
|||
isLock = false; |
|||
}); |
|||
} |
|||
}; |
|||
watch( |
|||
() => editorStore.scale, |
|||
() => { |
|||
drawThumbnail(); |
|||
} |
|||
); |
|||
watch( |
|||
() => editorStore.screenWidth, |
|||
() => { |
|||
drawThumbnail(); |
|||
} |
|||
); |
|||
watch( |
|||
() => editorStore.screenHeight, |
|||
() => { |
|||
drawThumbnail(); |
|||
} |
|||
); |
|||
watch( |
|||
() => editorStore.scrollLeft, |
|||
() => { |
|||
drawThumbnail(); |
|||
} |
|||
); |
|||
watch( |
|||
() => editorStore.scrollTop, |
|||
() => { |
|||
drawThumbnail(); |
|||
} |
|||
); |
|||
|
|||
watch( |
|||
() => editorStore.screenHeight, |
|||
() => { |
|||
drawThumbnail(); |
|||
} |
|||
); |
|||
watch( |
|||
() => editorStore.viewHeight, |
|||
() => { |
|||
drawThumbnail(); |
|||
} |
|||
); |
|||
watch( |
|||
() => editorStore.viewWidth, |
|||
() => { |
|||
drawThumbnail(); |
|||
} |
|||
); |
|||
|
|||
onMounted(() => { |
|||
window.addEventListener('resize', getViewBox); |
|||
getViewBox(); |
|||
}); |
|||
onBeforeUnmount(() => { |
|||
window.removeEventListener('resize', getViewBox); |
|||
}); |
|||
let moveInfo = { |
|||
startX: 0, |
|||
startY: 0, |
|||
isMove: false |
|||
}; |
|||
return { |
|||
canvasConfig, |
|||
thumbnailRef, |
|||
showThumbnail: computed(() => editorStore.isThumbnail), |
|||
onMoveStart: (e: MouseEvent) => { |
|||
moveInfo.isMove = true; |
|||
moveInfo.startX = e.clientX; |
|||
moveInfo.startY = e.clientY; |
|||
}, |
|||
onMove: (e: MouseEvent) => { |
|||
if (moveInfo.isMove) { |
|||
let left = editorStore.scrollLeft + (e.clientX - moveInfo.startX) * unscale.value; |
|||
let top = editorStore.scrollTop + (e.clientY - moveInfo.startY) * unscale.value; |
|||
// console.log('thumbnail', left, top); |
|||
editorStore.setScroll({ left, top }); |
|||
moveInfo.startX = e.clientX; |
|||
moveInfo.startY = e.clientY; |
|||
} |
|||
}, |
|||
onMoveEnd: () => { |
|||
moveInfo.isMove = false; |
|||
} |
|||
}; |
|||
} |
|||
}); |
|||
</script> |
|||
<style lang="scss" scoped> |
|||
.canvas-thumbnail { |
|||
background-color: var(--thumbnail-wrap-bg); |
|||
position: fixed; |
|||
right: 310px; |
|||
bottom: 48px; |
|||
z-index: 9; |
|||
cursor: move; |
|||
} |
|||
</style> |
|||
@ -1,41 +0,0 @@ |
|||
export const keyCode = { |
|||
space: 32, |
|||
backspace: 8, |
|||
shift: 16, |
|||
ctr: 17, |
|||
option: 18, |
|||
command: 91, |
|||
alt: 18, |
|||
ctrl: 17, |
|||
equal: 187, |
|||
minus: 189, |
|||
q: 81, |
|||
w: 87, |
|||
c: 67, |
|||
d: 68, |
|||
z: 90, |
|||
y: 89, |
|||
g: 71, |
|||
l: 76, |
|||
h: 72, |
|||
u: 85, |
|||
o: 79, |
|||
r: 82, |
|||
t: 84, |
|||
b: 66, |
|||
s: 83, |
|||
p: 80, |
|||
left: 37, |
|||
top: 38, |
|||
right: 39, |
|||
bottom: 40 |
|||
}; |
|||
|
|||
// export function getCodeKey(code) {
|
|||
// for (let k in keyCode) {
|
|||
// if (keyCode[k] == code) {
|
|||
// return k;
|
|||
// }
|
|||
// }
|
|||
// return '';
|
|||
// }
|
|||
@ -1,98 +0,0 @@ |
|||
/** |
|||
* 画标尺刻度Canvas |
|||
* @param direction 方向 |
|||
* @param canvas canvas DOM |
|||
* @param scale 缩放比例[20-200] |
|||
* @param width 长度 |
|||
* @param height 高度 |
|||
* @param startLen 开始距离 |
|||
*/ |
|||
export function drawRuler( |
|||
direction: string, |
|||
canvas: HTMLCanvasElement, |
|||
scale: number = 100, |
|||
width: number, |
|||
height: number = 24, |
|||
startLen: number = 60 |
|||
) { |
|||
const padding = 2; |
|||
const ctx: CanvasRenderingContext2D = canvas.getContext('2d'); |
|||
const percent = scale * 0.01; |
|||
let unit = Math.ceil(10 / percent); |
|||
if (unit < 8) { |
|||
unit = 8; |
|||
} |
|||
//计算出要绘制多少个刻度
|
|||
const scaleCount = Math.ceil(width + startLen / unit); |
|||
if (direction == 'top') { |
|||
canvas.width = width + startLen; |
|||
canvas.height = height; |
|||
ctx.clearRect(0, 0, width, height); |
|||
ctx.beginPath(); |
|||
//绘制起点
|
|||
ctx.strokeStyle = 'rgb(161, 174, 179)'; |
|||
ctx.font = '10px Arial'; |
|||
ctx.lineWidth = 0.5; |
|||
ctx.moveTo(startLen, 0); |
|||
ctx.lineTo(startLen, height); |
|||
ctx.fillText('0', startLen + padding, 13); |
|||
|
|||
for (let i = 1; i <= scaleCount; i++) { |
|||
//计算每个刻度的位置
|
|||
const step = startLen + Math.round(i * unit * percent); |
|||
//10的倍数刻度大长度
|
|||
if (i % 10 === 0) { |
|||
ctx.moveTo(step, 0); |
|||
ctx.lineTo(step, height); |
|||
//标注刻度值
|
|||
const scaleText = i * unit + ''; |
|||
ctx.fillText(scaleText, step + padding, 13); |
|||
} else if (i % 5 === 0) {//5的倍数刻度中长度
|
|||
ctx.moveTo(step, 15); |
|||
ctx.lineTo(step, height); |
|||
//标注刻度值
|
|||
const scaleText = i * unit + ''; |
|||
ctx.fillText(scaleText, step + padding, 13); |
|||
} else {//其他刻度小长度
|
|||
ctx.moveTo(step, height - 2); |
|||
ctx.lineTo(step, height); |
|||
} |
|||
} |
|||
ctx.stroke(); |
|||
} else { |
|||
canvas.width = height; |
|||
canvas.height = width + startLen; |
|||
ctx.clearRect(0, 0, height, width + startLen); |
|||
|
|||
ctx.beginPath(); |
|||
//绘制起点
|
|||
ctx.strokeStyle = 'rgb(161, 174, 179)'; |
|||
ctx.font = '10px Arial'; |
|||
ctx.lineWidth = 0.5; |
|||
ctx.moveTo(0, startLen); |
|||
ctx.lineTo(height, startLen); |
|||
ctx.fillText('0', padding, startLen - padding); |
|||
//计算出要绘制多少个刻度
|
|||
|
|||
for (let i = 1; i <= scaleCount; i++) { |
|||
const step = startLen + Math.round(i * unit * percent); |
|||
if (i % 10 === 0) { |
|||
ctx.moveTo(0, step); |
|||
ctx.lineTo(height, step); |
|||
//标注刻度值
|
|||
const scaleText = unit * i + ''; |
|||
ctx.fillText(scaleText, padding, step - padding); |
|||
} else if (i % 5 === 0) { |
|||
ctx.moveTo(15, step); |
|||
ctx.lineTo(height, step); |
|||
//标注刻度值
|
|||
const scaleText = unit * i + ''; |
|||
ctx.fillText(scaleText, padding, step - padding); |
|||
} else { |
|||
ctx.moveTo(height - 2, step); |
|||
ctx.lineTo(height, step); |
|||
} |
|||
} |
|||
ctx.stroke(); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue