vue3-sketch-ruler
Version:
> 是一个基于 Vue 3 + TypeScript 的标尺组件库,适用于低代码平台、大屏可视化、做图工具等场景,提供类似 Photoshop 的缩放与标尺辅助线体验。
245 lines (217 loc) • 6.98 kB
text/typescript
/**
* useSketchRuler - Master Composable
* 整合变换引擎、参考线状态、标尺样式计算,提供统一的 SketchRuler 状态管理入口
*/
import { computed, ref, watch, markRaw } from 'vue'
import type { ComputedRef, Ref, CSSProperties } from 'vue'
import { useCanvasTransform } from './useCanvasTransform'
import {
produceState,
createDefaultState,
type RulerState,
type RulerAction,
type GuideLine,
type RulerPalette,
type TransformEngine
} from '@sketch-ruler/core'
import type { RulerContext } from '../state/ruler-context'
import { RulerContextKey } from '../state/ruler-context'
export interface SketchRulerOptions {
width: number
height: number
canvasWidth: number
canvasHeight: number
thick?: number
scale?: number
palette?: Partial<RulerPalette>
lines?: { h: number[]; v: number[] }
snapThreshold?: number
lockLine?: boolean
minZoom?: number
maxZoom?: number
zoomStep?: number
autoCenter?: boolean
showRuler?: boolean
}
export interface UseSketchRulerReturn {
engine: TransformEngine
scale: Ref<number>
offset: Ref<{ x: number; y: number }>
rectWidth: ComputedRef<number>
rectHeight: ComputedRef<number>
rectStyle: ComputedRef<CSSProperties>
canvasStyle: ComputedRef<CSSProperties>
cornerStyle: ComputedRef<CSSProperties>
paletteCpu: ComputedRef<RulerPalette>
horizontalLines: ComputedRef<GuideLine[]>
verticalLines: ComputedRef<GuideLine[]>
state: Ref<RulerState>
zoomIn: () => void
zoomOut: () => void
reset: () => void
panBy: (dx: number, dy: number) => void
setTransform: (t: { scale?: number; x?: number; y?: number }) => void
addLine: (line: Omit<GuideLine, 'id'>) => void
moveLine: (id: string, position: number) => void
removeLine: (id: string) => void
toggleLineLock: (id: string) => void
context: RulerContext
}
export function useSketchRuler(options: SketchRulerOptions): UseSketchRulerReturn {
const thick = options.thick ?? 16
const width = options.width
const height = options.height
const canvasWidth = options.canvasWidth
const canvasHeight = options.canvasHeight
const rectWidth = computed(() => width)
const rectHeight = computed(() => height)
const { scale, offset, engine, setTransform, zoomBy, zoomTo, panBy, reset } = useCanvasTransform({
initialScale: options.scale ?? 1,
initialOffset: { x: 0, y: 0 },
minZoom: options.minZoom ?? 0.1,
maxZoom: options.maxZoom ?? 10,
autoCenter: options.autoCenter ?? true,
canvasSize: { width: canvasWidth, height: canvasHeight },
viewportSize: { width: rectWidth.value, height: rectHeight.value },
paddingRatio: 0.2
})
// 状态管理:produceState + ref
const state = ref<RulerState>(createDefaultState())
// 合并外部 palette
if (options.palette) {
state.value = produceState(state.value, { type: 'setPalette', palette: options.palette })
}
// 合并外部 snapThreshold
if (options.snapThreshold !== undefined) {
state.value = produceState(state.value, {
type: 'setSnapConfig',
config: { threshold: options.snapThreshold }
})
}
// 导入初始参考线
if (options.lines) {
const newLines: GuideLine[] = []
let id = 0
for (const h of options.lines.h) {
newLines.push({
id: `h-${id++}-${Date.now()}`,
orientation: 'h',
position: h,
visible: true,
locked: false
})
}
for (const v of options.lines.v) {
newLines.push({
id: `v-${id++}-${Date.now()}`,
orientation: 'v',
position: v,
visible: true,
locked: false
})
}
state.value = produceState(state.value, { type: 'setLines', lines: newLines })
}
// 同步 lockLine prop 到全局参考线状态
if (options.lockLine !== undefined && options.lockLine) {
state.value = produceState(state.value, {
type: 'setLines',
lines: state.value.lines.map((l) => ({ ...l, locked: true }))
})
}
const paletteCpu = computed<RulerPalette>(() => state.value.palette)
const rectStyle = computed<CSSProperties>(() => ({
background: paletteCpu.value.bgColor,
width: rectWidth.value + 'px',
height: rectHeight.value + 'px',
left: 0,
top: 0
}))
const canvasStyle = computed<CSSProperties>(() => ({
width: canvasWidth + 'px',
height: canvasHeight + 'px'
}))
const cornerStyle = computed<CSSProperties>(() => ({
width: thick + 'px',
height: thick + 'px',
borderRight: `1px solid ${paletteCpu.value.borderColor}`,
borderBottom: `1px solid ${paletteCpu.value.borderColor}`
}))
const horizontalLines = computed(() =>
state.value.lines.filter((l) => l.orientation === 'h' && l.visible !== false)
)
const verticalLines = computed(() =>
state.value.lines.filter((l) => l.orientation === 'v' && l.visible !== false)
)
// 方法封装
const dispatch = (action: RulerAction): void => {
state.value = produceState(state.value, action)
}
const addLine = (line: Omit<GuideLine, 'id'>): void => {
const id = `line-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`
dispatch({ type: 'addLine', line: { ...line, id } })
}
const moveLine = (id: string, position: number): void => {
dispatch({ type: 'moveLine', id, position })
}
const removeLine = (id: string): void => {
dispatch({ type: 'removeLine', id })
}
const toggleLineLock = (id: string): void => {
const line = state.value.lines.find((l) => l.id === id)
if (line) {
dispatch({ type: 'updateLine', id, updates: { locked: !line.locked } })
}
}
const zoomIn = (): void => {
const step = options.zoomStep ?? 0.25
zoomBy(step, canvasWidth / 2, canvasHeight / 2)
}
const zoomOut = (): void => {
const step = options.zoomStep ?? 0.25
zoomBy(-step, canvasWidth / 2, canvasHeight / 2)
}
// provide/inject 上下文
const viewportSize = ref({ width: rectWidth.value, height: rectHeight.value })
const contentSize = ref({ width: canvasWidth, height: canvasHeight })
watch([() => width, () => height, () => canvasWidth, () => canvasHeight], () => {
viewportSize.value = { width, height }
contentSize.value = { width: canvasWidth, height: canvasHeight }
})
const context: RulerContext = {
scale,
offset,
viewportSize,
contentSize,
lines: computed(() => state.value.lines) as Ref<GuideLine[]>,
snapConfig: state.value.snapConfig,
palette: paletteCpu.value,
engine: markRaw(engine),
showRuler: ref(options.showRuler ?? true),
showReferLine: computed(() => state.value.showReferLine) as Ref<boolean>
}
return {
engine,
scale,
offset,
rectWidth,
rectHeight,
rectStyle,
canvasStyle,
cornerStyle,
paletteCpu,
horizontalLines,
verticalLines,
state,
zoomIn,
zoomOut,
reset,
panBy,
setTransform,
addLine,
moveLine,
removeLine,
toggleLineLock,
context
}
}