Explorar o código

feat:贝塞尔曲线编辑器新增预制项

Grnetsky hai 3 semanas
pai
achega
e6bf932220

+ 8 - 0
src/views/components/PenAnimates.vue

@@ -157,6 +157,7 @@
                 style="width: 200px"
                 v-model="item.animateTimingFunction"
                 @change="changeValue(i)"
+                :preset="bezierPreset"
               ></BezierEditor>
             </div>
             <div class="form-item mt-8">
@@ -344,6 +345,13 @@ function changeAnimateAutoPlay(value, item) {
     }
   }
 }
+
+const bezierPreset = [
+    '0.25,0.25,0.75,0.75',
+    '0.25,0.1,0.25,1',
+    '0.42,0,0.58,1',
+    '0,0,0.58,1',
+  ]
 const checkAnimateName = (item:any) => {
   if(!item.name) {
     MessagePlugin.warning($t('动画名不能为空!'));

+ 87 - 31
src/views/components/common/BezierEditor.vue

@@ -1,35 +1,50 @@
 <template>
   <div class="bezier-editor">
-    <svg
-        ref="svg"
-        class="editor"
-        viewBox="0 0 1 1"
-        preserveAspectRatio="none"
-        @mousedown="onSvgMouseDown"
-    >
-
-      <line x1="0" y1="1" x2="1" y2="0" stroke="#88888893" stroke-width="0.01" />
-
-      <line :x1="0" :y1="1" :x2="p1.x" :y2="p1.y" stroke="#4583FF" stroke-dasharray="2,2" stroke-width="0.015" />
-      <line :x1="p2.x" :y1="p2.y" :x2="1" :y2="0" stroke="#4583FF" stroke-dasharray="2,2" stroke-width="0.015" />
-
-      <path :d="curvePath" fill="none" stroke="#E3E8F4" stroke-width="0.02" />
-
-      <circle
-          class="point"
-          r="0.03"
-          :cx="p1.x"
-          :cy="p1.y"
-          @mousedown.prevent="startDrag('p1')"
-      />
-      <circle
-          class="point"
-          r="0.03"
-          :cx="p2.x"
-          :cy="p2.y"
-          @mousedown.prevent="startDrag('p2')"
-      />
-    </svg>
+
+    <div class="main">
+      <svg
+          ref="svg"
+          class="editor"
+          viewBox="0 0 1 1"
+          preserveAspectRatio="none"
+          @mousedown="onSvgMouseDown"
+      >
+
+        <line x1="0" y1="1" x2="1" y2="0" stroke="#88888893" stroke-width="0.01" />
+
+        <line :x1="0" :y1="1" :x2="p1.x" :y2="p1.y" stroke="#4583FF" stroke-dasharray="2,2" stroke-width="0.015" />
+        <line :x1="p2.x" :y1="p2.y" :x2="1" :y2="0" stroke="#4583FF" stroke-dasharray="2,2" stroke-width="0.015" />
+
+        <path :d="curvePath" fill="none" stroke="#E3E8F4" stroke-width="0.02" />
+
+        <circle
+            class="point"
+            r="0.03"
+            :cx="p1.x"
+            :cy="p1.y"
+            @mousedown.prevent="startDrag('p1')"
+        />
+        <circle
+            class="point"
+            r="0.03"
+            :cx="p2.x"
+            :cy="p2.y"
+            @mousedown.prevent="startDrag('p2')"
+        />
+      </svg>
+
+      <div class="preset">
+        <div v-for="(item, index) in preset" :key="index" class="presetItem" @click="onPresetClick(item)">
+          <svg
+              :class="{presetItemSvg:true,active:modelValue===item}"
+              viewBox="0 0 1 1"
+              preserveAspectRatio="none"
+          >
+            <path :d="'M 0 1 C ' + parsePresetStr(item)+ ',1 0'" fill="none" stroke="#E3E8F4" stroke-width="0.04" />
+          </svg>
+        </div>
+      </div>
+    </div>
 
     <div class="controls">
       <div class="label">{{ modelValue }}</div>
@@ -67,6 +82,12 @@ function formatVal() {
   return `${x1},${y1},${x2},${y2}`
 }
 
+function onPresetClick(item) {
+  parseRaw(item)
+  emit('update:modelValue', formatVal())
+  emit('change', formatVal())
+}
+
 const parseRaw = (str) => {
   if(!str) return
   const nums = str.split(',').map(Number)
@@ -84,6 +105,13 @@ const curvePath = computed(() => {
   return `M 0 1 C ${p1.value.x} ${p1.value.y}, ${p2.value.x} ${p2.value.y}, 1 0`
 })
 
+function parsePresetStr(str) {
+  if(!str) return
+  const nums = str.split(',').map(Number)
+  if (nums.length === 4 && nums.every(n => !isNaN(n))) {
+    return `${nums[0]} ${1 - nums[1]}, ${nums[2]} ${1 - nums[3]}`
+  }
+}
 function startDrag(name) {
   dragging.value = name
 }
@@ -161,11 +189,15 @@ onBeforeUnmount(() => {
   align-items: center;
   user-select: none;
 }
-.editor {
+.main {
   width: 100%;
   aspect-ratio:1;
   border: 1px solid #303746;
   background: #303746;
+  border-radius: 4px;
+}
+.editor {
+  padding: 20px;
 }
 .point {
   fill: #4583FF;
@@ -181,4 +213,28 @@ onBeforeUnmount(() => {
   justify-content: space-between;
   align-items: center;
 }
+.preset{
+  width: 100%;
+  display: flex;
+  justify-content: space-around;
+  align-items: center;
+  margin-bottom: 5px;
+}
+
+.presetItem{
+  width: 34px;
+  height: 34px;
+  background-color: #1e2430;
+  border-radius: 4px;
+}
+.presetItem:hover{
+  cursor: pointer;
+}
+
+.presetItemSvg{
+  padding: 4px;
+}
+.active{
+  background-color: #262D3A;
+}
 </style>