极简svg编辑器(可以拖拽、放大、旋转)

 

背景

现在市面上有很多的svg编辑器。但是好一点的需要收费,一般的使用起来,定制化不行。现在我用最基本的svg写了一个核心不到200行的可以拖拽、放大、旋转的功能。如果想要定制化添加事件,可以自己添加。

核心思想

通过svg进行画布的设置,<g>标签进行一个个元素的分组。如何进行拖拽、放大、旋转?。核心思想通过transform进行。

具体实现

vue3  + vueuse + ts

核心组件

<template>
  <svg
      xmlns="http://www.w3.org/2000/svg"
      style="background-color:#000000;"
      width="100vw"
      height="100vh"
      ref="svg_dom_ref"
      @click="clear"
  >
    <g v-for="(item, idx) in svgLists"  :key="item.id"
       :id="item.id"
       :transform="'translate(' + (item.x) + ',' + (item.y) + ')' + 'rotate(' + item.angle + ')' + 'scale(' + item.size + ')'"
       @click="selectEle(idx, $event)"
       @mousedown="selectEle(idx, $event)"
       v-html="item.template"
    >
    </g>
    <SelectSvg v-if="curSelect" :vNode="curSelect" @changeTranslate="changeTranslate"/>
  </svg>
</template>

<script setup>
import SelectSvg from './SelectSvg.vue'
import {ref, watch, unref} from "vue";
import {useMouse, useMousePressed} from "@vueuse/core";
const svgLists = ref([{
  id: '1',
  "template": '<path fill="#FF0000" stroke="#FF0000" stroke-width="5" d="m0,0 c14.617751633754164,-41.93617271978648 71.89058180534832,0 0,53.91793635401125 c-71.89058180534832,-53.91793635401125 -14.617751633754164,-95.85410907379776 0,-53.91793635401125 z" fill-opacity="1" stroke-opacity="1"  style="pointer-events: inherit;"></path>',
  "props": [
    "prop_data"
  ],
  x: 100,
  y: 100,
  size: 3,
  angle: 0
}])
const curSelect = ref();
const moveEle = ref();

const selectEle = (idx) => {
  moveEle.value = svgLists.value[idx];
  curSelect.value = svgLists.value[idx];
}

const changeTranslate = (x, y, size, angle) => {
  curSelect.value.x = x;
  curSelect.value.y = y;
  curSelect.value.size = size || 1;
  curSelect.value.angle = angle || 0;
}

const { pressed } = useMousePressed()
const { x, y } = useMouse()

watch(() => pressed.value, () => {
  if(!pressed.value) {
    setTimeout(() => moveEle.value = undefined)
  }
})
watch([() => x.value, () => y.value], () => {
  if(pressed.value) {
    if(moveEle.value) {
      const element = document.getElementById(moveEle.value.id);
      const {x: cx, y: cy , width, height} = element.getBBox();
      moveEle.value.x = unref(x) + width /2 + cx;
      moveEle.value.y = unref(y)  + cy;
    }
  }
})
</script>

<style scoped>

</style>

选择器组件

<template>
  <g id="selectorParentGroup" :transform="'translate(' + (vNode.x) + ',' + (vNode.y) + ')' + 'rotate(' + vNode.angle + ')' + 'scale(' + vNode.size + ')'">
    <rect id="selectorRubberBand" fill="#22C" fill-opacity="0.15" stroke="#22C" stroke-width="0.5" display="none"
          style="pointer-events:none" :x="bBox.x - bBox.width" :y="bBox.y" :width="bBox.width" :height="bBox.height"></rect>
    <g id="selectorGroup0" transform="" display="inline">
      <path id="selectedBox0" fill="none" stroke="#22C" stroke-dasharray="5,5" style="pointer-events:none"
            :d="`M${bBox.x},${bBox.y} L${bBox.x + bBox.width},${bBox.y} ${bBox.x + bBox.width},${bBox.y + bBox.height} ${bBox.x},${bBox.y + bBox.height}Z`"></path>
      <g display="inline">
        <circle ref="el" fill="#22C" r="4" style="cursor:nw-resize" stroke-width="2"
                pointer-events="all" :cx="bBox.x" :cy="bBox.y"></circle>
        <circle id="selectorGrip_resize_n" fill="#22C" r="4" style="cursor:n-resize" stroke-width="2"
                pointer-events="all" :cx="bBox.x + bBox.width/2" :cy="bBox.y" ></circle>
        <circle id="selectorGrip_resize_ne" fill="#22C" r="4" style="cursor:ne-resize" stroke-width="2"
                pointer-events="all" :cx="bBox.x + bBox.width" :cy="bBox.y"></circle>
        <circle id="selectorGrip_resize_e" fill="#22C" r="4" style="cursor:e-resize" stroke-width="2"
                pointer-events="all" :cx="bBox.x + bBox.width" :cy="bBox.y + bBox.height/2"></circle>
        <circle id="selectorGrip_resize_se" fill="#22C" r="4" style="cursor:se-resize" stroke-width="2"
                pointer-events="all" :cx="bBox.x + bBox.width" :cy="bBox.y + bBox.height"></circle>
        <circle id="selectorGrip_resize_s" fill="#22C" r="4" style="cursor:s-resize" stroke-width="2"
                pointer-events="all" :cx="bBox.x + bBox.width/2" :cy="bBox.y + bBox.height"></circle>
        <circle id="selectorGrip_resize_sw" fill="#22C" r="4" style="cursor:sw-resize" stroke-width="2"
                pointer-events="all" :cx="bBox.x" :cy="bBox.y + bBox.height"></circle>
        <circle id="selectorGrip_resize_w" fill="#22C" r="4" style="cursor:w-resize" stroke-width="2"
                pointer-events="all" :cx="bBox.x" :cy="bBox.y + bBox.height/2"></circle>
        <line id="selectorGrip_rotateconnector" stroke="#22C" :x1="bBox.x + bBox.width/2" :y1="bBox.y -20" :x2="bBox.x + bBox.width/2"
              :y2="bBox.y"></line>
        <circle ref="rotate" fill="lime" r="4" stroke="#22C" stroke-width="2"
                style="cursor:url(../assets/rotate.svg) 12 12, auto;" :cx="bBox.x + bBox.width/2" :cy="bBox.y -20"></circle>
      </g>
    </g>
  </g>
</template>

<script setup>
import {ref, unref, watch} from "vue";
import { useMousePressed, useMouse } from '@vueuse/core'

const props = defineProps({
  vNode: Object
});
const bBox = ref({x: 0, width: 0, height: 0, y: 0})
watch(() =>props.vNode, () => {
  if(props.vNode && props.vNode.id) {
    const element = document.getElementById(props.vNode.id);
    if(element) {
      bBox.value = element.getBBox();
    }
  }
}, {immediate: true})
const el = ref(null)
const rotate = ref(null)

const { x, y } = useMouse()
const { pressed } = useMousePressed({target: el })
const { pressed:  rotatePressed} = useMousePressed({target: rotate })
const emits = defineEmits(['changeTranslate']);
watch([() => x.value,() => y.value], ([x, y]) => {
  if(pressed.value && props.vNode) {
    const {angle,x: cx, y: cy} = props.vNode;
    let size = (cx - x + bBox.value.width)/ bBox.value.width * 1.4;
    console.log(cx, cy, x, y)
    emits('changeTranslate',  cx, cy , size,  angle)
  }
  if(rotatePressed.value && props.vNode) {
    // const {angle,x: cx, y: cy} = props.vNode;
    // let a = (cx - x + bBox.value.width)/ bBox.value.width * 1.4;
    // emits('changeTranslate',  cx, cy , a,  angle)
  }
})
</script>

<style scoped>

</style>

You May Also Like

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注