pipipi-pikachu 5 سال پیش
والد
کامیت
653bf033a7

+ 7 - 57
src/views/Editor/Canvas/Operate/ImageElementOperate.vue

@@ -1,24 +1,5 @@
 <template>
-  <div 
-    class="clip-wrapper" 
-    :style="{
-      width: elementInfo.width + 'px',
-      height: elementInfo.height + 'px',
-    }"
-    v-if="isCliping"
-  >
-    <ImageClipHandler
-      :src="elementInfo.src"
-      :clipData="elementInfo.clip"
-      :width="elementInfo.width"
-      :height="elementInfo.height"
-      :top="elementInfo.top"
-      :left="elementInfo.left"
-      :clipPath="clipShape.style"
-      @clip="range => clip(range)"
-    />
-  </div>
-  <div class="image-element-operate" v-else>
+  <div class="image-element-operate" :class="{ 'cliping': isCliping }">
     <BorderLine 
       class="operate-border-line"
       v-for="line in borderLines" 
@@ -47,16 +28,14 @@
 <script lang="ts">
 import { computed, defineComponent, PropType } from 'vue'
 import { useStore } from 'vuex'
-import { MutationTypes, State } from '@/store'
+import { State } from '@/store'
 import { PPTImageElement } from '@/types/slides'
-import { OperateResizeHandler, ImageClipedEmitData } from '@/types/edit'
-import { CLIPPATHS, ClipPathTypes } from '@/configs/imageClip'
+import { OperateResizeHandler } from '@/types/edit'
 import useCommonOperate from '../hooks/useCommonOperate'
 
 import RotateHandler from './RotateHandler.vue'
 import ResizeHandler from './ResizeHandler.vue'
 import BorderLine from './BorderLine.vue'
-import ImageClipHandler from './ImageClipHandler.vue'
 
 export default defineComponent({
   name: 'image-element-operate',
@@ -65,7 +44,6 @@ export default defineComponent({
     RotateHandler,
     ResizeHandler,
     BorderLine,
-    ImageClipHandler,
   },
   props: {
     elementInfo: {
@@ -93,52 +71,24 @@ export default defineComponent({
     const store = useStore<State>()
     const canvasScale = computed(() => store.state.canvasScale)
     const clipingImageElementId = computed(() => store.state.clipingImageElementId)
+    const isCliping = computed(() => clipingImageElementId.value === props.elementInfo.id)
 
     const scaleWidth = computed(() => props.elementInfo.width * canvasScale.value)
     const scaleHeight = computed(() => props.elementInfo.height * canvasScale.value)
     const { resizeHandlers, borderLines } = useCommonOperate(scaleWidth, scaleHeight)
 
-    const clipShape = computed(() => {
-      if(!props.elementInfo || !props.elementInfo.clip) return CLIPPATHS.rect
-      const shape = props.elementInfo.clip.shape || ClipPathTypes.RECT
-
-      return CLIPPATHS[shape]
-    })
-
-    const isCliping = computed(() => clipingImageElementId.value === props.elementInfo.id)
-
-    const clip = (data: ImageClipedEmitData) => {
-      store.commit(MutationTypes.SET_CLIPING_IMAGE_ELEMENT_ID, '')
-      
-      if(!data) return
-
-      const { range, position } = data
-      const originClip = props.elementInfo.clip || {}
-      
-      const _props = {
-        clip: { ...originClip, range },
-        left: props.elementInfo.left + position.left,
-        top: props.elementInfo.top + position.top,
-        width: props.elementInfo.width + position.width,
-        height: props.elementInfo.height + position.height,
-      }
-      console.log(_props)
-    }
-
     return {
-      clipShape,
+      isCliping,
       scaleWidth,
       resizeHandlers,
       borderLines,
-      isCliping,
-      clip,
     }
   },
 })
 </script>
 
 <style lang="scss" scoped>
-.clip-wrapper {
-  position: relative;
+.image-element-operate.cliping {
+  visibility: hidden;
 }
 </style>

+ 495 - 493
src/views/Editor/Canvas/Operate/ImageClipHandler.vue

@@ -1,494 +1,496 @@
-<template>
-  <div 
-    class="image-clip-handler" 
-    :style="clipWrapperPositionStyle" 
-    v-click-outside="clip"
-  >
-    <img 
-      class="bottom-img" 
-      :src="src" 
-      :draggable="false" 
-      alt="" 
-      :style="bottomImgPositionStyle" 
-    />
-
-    <div 
-      class="top-image-content" 
-      :style="{
-        ...topImgWrapperPositionStyle,
-        clipPath,
-      }"
-    >
-      <img 
-        class="top-img" 
-        :src="src" 
-        :draggable="false" 
-        alt="" 
-        :style="topImgPositionStyle" 
-      />
-    </div>
-
-    <div 
-      class="operate" 
-      :style="topImgWrapperPositionStyle" 
-      @mousedown.stop="$event => moveClipRange($event)"
-    >
-      <div 
-        :class="['clip-point', point]" 
-        v-for="point in ['t-l', 't-r', 'b-l', 'b-r']" 
-        :key="point" 
-        @mousedown.stop="$event => scaleClipRange($event, point)"
-      >
-        <SvgWrapper width="12" height="12" fill="#fff" stroke="#666">
-          <path d="M 12 0 L 0 0 L 0 12 L 3 12 L 3 3 L 12 3 L 12 0 Z"></path>
-        </SvgWrapper>
-      </div>
-    </div>
-  </div>
-</template>
-
-<script lang="ts">
-import { computed, defineComponent, onMounted, onUnmounted, PropType, reactive, ref } from 'vue'
-import { useStore } from 'vuex'
-import { State } from '@/store'
-import { KEYS } from '@/configs/hotkey'
-import { ImageClipData, ImageClipDataRange, ImageClipedEmitData } from '@/types/edit'
-
-type ScaleClipRangeType = 't-l' | 't-r' | 'b-l' | 'b-r'
-
-export default defineComponent({
-  name: 'image-clip-handler',
-  props: {
-    src: {
-      type: String,
-      required: true,
-    },
-    clipData: {
-      type: Object as PropType<ImageClipData>,
-    },
-    clipPath: {
-      type: String,
-      required: true,
-    },
-    width: {
-      type: Number,
-      required: true,
-    },
-    height: {
-      type: Number,
-      required: true,
-    },
-    top: {
-      type: Number,
-      required: true,
-    },
-    left: {
-      type: Number,
-      required: true,
-    },
-  },
-  setup(props, { emit }) {
-    const store = useStore<State>()
-    const canvasScale = computed(() => store.state.canvasScale)
-
-    const topImgWrapperPosition = reactive({
-      top: 0,
-      left: 0,
-      width: 0,
-      height: 0,
-    })
-    const clipWrapperPositionStyle = reactive({
-      top: '0',
-      left: '0',
-    })
-    const isSettingClipRange = ref(false)
-    const currentRange = ref<ImageClipDataRange | null>(null)
-
-    const getClipDataTransformInfo = () => {
-      const [start, end] = props.clipData ? props.clipData.range : [[0, 0], [100, 100]]
-
-      const widthScale = (end[0] - start[0]) / 100
-      const heightScale = (end[1] - start[1]) / 100
-      const left = start[0] / widthScale
-      const top = start[1] / heightScale
-
-      return { widthScale, heightScale, left, top }
-    }
-
-    const imgPosition = computed(() => {
-      const { widthScale, heightScale, left, top } = getClipDataTransformInfo()
-      return {
-        left: -left,
-        top: -top,
-        width: 100 / widthScale,
-        height: 100 / heightScale,
-      }
-    })
-
-    const bottomImgPositionStyle = computed(() => {
-      return {
-        top: imgPosition.value.top + '%',
-        left: imgPosition.value.left + '%',
-        width: imgPosition.value.width + '%',
-        height: imgPosition.value.height + '%',
-      }
-    })
-
-    const topImgWrapperPositionStyle = computed(() => {
-      return {
-        top: topImgWrapperPosition.top + '%',
-        left: topImgWrapperPosition.left + '%',
-        width: topImgWrapperPosition.width + '%',
-        height: topImgWrapperPosition.height + '%',
-      }
-    })
-
-    const topImgPositionStyle = computed(() => {
-      const bottomWidth = imgPosition.value.width
-      const bottomHeight = imgPosition.value.height
-      
-      const topLeft = topImgWrapperPosition.left
-      const topTop = topImgWrapperPosition.top
-      const topWidth = topImgWrapperPosition.width
-      const topHeight = topImgWrapperPosition.height
-      
-      return {
-        left: -topLeft * (100 / topWidth) + '%',
-        top: -topTop * (100 / topHeight) + '%',
-        width: bottomWidth / topWidth * 100 + '%',
-        height: bottomHeight / topHeight * 100 + '%',
-      }
-    })
-
-    const initClipPosition = () => {
-      const { left, top } = getClipDataTransformInfo()
-      topImgWrapperPosition.left = left
-      topImgWrapperPosition.top = top
-      topImgWrapperPosition.width = 100
-      topImgWrapperPosition.height = 100
-      
-      clipWrapperPositionStyle.top = -top + '%'
-      clipWrapperPositionStyle.left = -left + '%'
-    }
-
-    const clip = () => {
-      if(isSettingClipRange.value || !currentRange.value) {
-        emit('clip', null)
-        return
-      }
-
-      const { left, top } = getClipDataTransformInfo()
-
-      const position = {
-        left: (topImgWrapperPosition.left - left) / 100 * props.width,
-        top: (topImgWrapperPosition.top - top) / 100 * props.height,
-        width: (topImgWrapperPosition.width - 100) / 100 * props.width,
-        height: (topImgWrapperPosition.height - 100) / 100 * props.height,
-      }
-
-      const clipedEmitData: ImageClipedEmitData = {
-        range: currentRange.value,
-        position,
-      }
-      emit('clip', clipedEmitData)
-    }
-
-    const keyboardClip = (e: KeyboardEvent) => {
-      const key = e.key.toUpperCase()
-      if(key === KEYS.ENTER) clip()
-    }
-
-    onMounted(() => {
-      initClipPosition()
-      document.addEventListener('keydown', keyboardClip)
-    })
-    onUnmounted(() => {
-      document.removeEventListener('keydown', keyboardClip)
-    })
-
-    const getRange = () => {
-      const retPosition = {
-        left: parseInt(topImgPositionStyle.value.left),
-        top: parseInt(topImgPositionStyle.value.top),
-        width: parseInt(topImgPositionStyle.value.width),
-        height: parseInt(topImgPositionStyle.value.height),
-      }
-
-      const widthScale = 100 / retPosition.width
-      const heightScale = 100 / retPosition.height
-
-      const start: [number, number] = [
-        -retPosition.left * widthScale,
-        -retPosition.top * heightScale,
-      ]
-      const end: [number, number] = [
-        widthScale * 100 + start[0],
-        heightScale * 100 + start[1],
-      ]
-
-      currentRange.value = [start, end]
-    }
-
-    const moveClipRange = (e: MouseEvent) => {
-      isSettingClipRange.value = true
-      let isMouseDown = true
-
-      const startPageX = e.pageX
-      const startPageY = e.pageY
-      const bottomPosition = imgPosition.value
-      const originPositopn = {
-        left: topImgWrapperPosition.left,
-        top: topImgWrapperPosition.top,
-        width: topImgWrapperPosition.width,
-        height: topImgWrapperPosition.height,
-      }
-
-      document.onmousemove = e => {
-        if(!isMouseDown) return
-
-        const currentPageX = e.pageX
-        const currentPageY = e.pageY
-
-        const moveX = (currentPageX - startPageX) / canvasScale.value / props.width * 100
-        const moveY = (currentPageY - startPageY) / canvasScale.value / props.height * 100
-
-        let targetLeft = originPositopn.left + moveX
-        let targetTop = originPositopn.top + moveY
-
-        // 范围限制
-        if(targetLeft < 0) targetLeft = 0
-        else if(targetLeft + originPositopn.width > bottomPosition.width) {
-          targetLeft = bottomPosition.width - originPositopn.width
-        }
-        if(targetTop < 0) targetTop = 0
-        else if(targetTop + originPositopn.height > bottomPosition.height) {
-          targetTop = bottomPosition.height - originPositopn.height
-        }
-        
-        topImgWrapperPosition.left = targetLeft
-        topImgWrapperPosition.top = targetTop
-      }
-
-      document.onmouseup = () => {
-        isMouseDown = false
-        document.onmousemove = null
-        document.onmouseup = null
-
-        getRange()
-
-        setTimeout(() => {
-          isSettingClipRange.value = false
-        }, 0)
-      }
-    }
-
-    const scaleClipRange = (e: MouseEvent, type: ScaleClipRangeType) => {
-      isSettingClipRange.value = true
-      let isMouseDown = true
-
-      const minWidth = 32 / props.width * 100
-      const minHeight = 32 / props.height * 100
-      
-      const startPageX = e.pageX
-      const startPageY = e.pageY
-      const bottomPosition = imgPosition.value
-      const originPositopn = {
-        left: topImgWrapperPosition.left,
-        top: topImgWrapperPosition.top,
-        width: topImgWrapperPosition.width,
-        height: topImgWrapperPosition.height,
-      }
-
-      document.onmousemove = e => {
-        if(!isMouseDown) return
-
-        const currentPageX = e.pageX
-        const currentPageY = e.pageY
-
-        let moveX = (currentPageX - startPageX) / canvasScale.value / props.width * 100
-        let moveY = (currentPageY - startPageY) / canvasScale.value / props.height * 100
-
-        let targetLeft, targetTop, targetWidth, targetHeight
-
-        // 根据不同缩放点,计算目标大小和位置,同时做大小和范围的限制
-        if(type === 't-l') {
-          if(originPositopn.left + moveX < 0) {
-            moveX = -originPositopn.left
-          }
-          if(originPositopn.top + moveY < 0) {
-            moveY = -originPositopn.top
-          }
-          if(originPositopn.width - moveX < minWidth) {
-            moveX = originPositopn.width - minWidth
-          }
-          if(originPositopn.height - moveY < minHeight) {
-            moveY = originPositopn.height - minHeight
-          }
-          targetWidth = originPositopn.width - moveX
-          targetHeight = originPositopn.height - moveY
-          targetLeft = originPositopn.left + moveX
-          targetTop = originPositopn.top + moveY
-        }
-        else if(type === 't-r') {
-          if(originPositopn.left + originPositopn.width + moveX > bottomPosition.width) {
-            moveX = bottomPosition.width - (originPositopn.left + originPositopn.width)
-          }
-          if(originPositopn.top + moveY < 0) {
-            moveY = -originPositopn.top
-          }
-          if(originPositopn.width + moveX < minWidth) {
-            moveX = minWidth - originPositopn.width
-          }
-          if(originPositopn.height - moveY < minHeight) {
-            moveY = originPositopn.height - minHeight
-          }
-          targetWidth = originPositopn.width + moveX
-          targetHeight = originPositopn.height - moveY
-          targetLeft = originPositopn.left
-          targetTop = originPositopn.top + moveY
-        }
-        else if(type === 'b-l') {
-          if(originPositopn.left + moveX < 0) {
-            moveX = -originPositopn.left
-          }
-          if(originPositopn.top + originPositopn.height + moveY > bottomPosition.height) {
-            moveY = bottomPosition.height - (originPositopn.top + originPositopn.height)
-          }
-          if(originPositopn.width - moveX < minWidth) {
-            moveX = originPositopn.width - minWidth
-          }
-          if(originPositopn.height + moveY < minHeight) {
-            moveY = minHeight - originPositopn.height
-          }
-          targetWidth = originPositopn.width - moveX
-          targetHeight = originPositopn.height + moveY
-          targetLeft = originPositopn.left + moveX
-          targetTop = originPositopn.top
-        }
-        else {
-          if(originPositopn.left + originPositopn.width + moveX > bottomPosition.width) {
-            moveX = bottomPosition.width - (originPositopn.left + originPositopn.width)
-          }
-          if(originPositopn.top + originPositopn.height + moveY > bottomPosition.height) {
-            moveY = bottomPosition.height - (originPositopn.top + originPositopn.height)
-          }
-          if(originPositopn.width + moveX < minWidth) {
-            moveX = minWidth - originPositopn.width
-          }
-          if(originPositopn.height + moveY < minHeight) {
-            moveY = minHeight - originPositopn.height
-          }
-          targetWidth = originPositopn.width + moveX
-          targetHeight = originPositopn.height + moveY
-          targetLeft = originPositopn.left
-          targetTop = originPositopn.top
-        }
-        
-        topImgWrapperPosition.left = targetLeft
-        topImgWrapperPosition.top = targetTop
-        topImgWrapperPosition.width = targetWidth
-        topImgWrapperPosition.height = targetHeight
-      }
-
-      document.onmouseup = () => {
-        isMouseDown = false
-        document.onmousemove = null
-        document.onmouseup = null
-
-        getRange()
-
-        setTimeout(() => isSettingClipRange.value = false, 0)
-      }
-    }
-
-    return {
-      clipWrapperPositionStyle,
-      bottomImgPositionStyle,
-      topImgWrapperPositionStyle,
-      topImgPositionStyle,
-      clip,
-      moveClipRange,
-      scaleClipRange,
-    }
-  },
-})
-</script>
-
-<style lang="scss" scoped>
-.image-clip-handler {
-  width: 100%;
-  height: 100%;
-  position: relative;
-
-  .bottom-img {
-    top: 0;
-    left: 0;
-    width: 100%;
-    height: 100%;
-    opacity: .5;
-  }
-
-  img {
-    width: 100%;
-    height: 100%;
-  }
-
-  .top-image-content {
-    position: absolute;
-    overflow: hidden;
-
-    img {
-      position: absolute;
-    }
-  }
-}
-
-.operate {
-  position: absolute;
-  width: 100%;
-  height: 100%;
-  top: 0;
-  left: 0;
-  cursor: move;
-}
-
-.clip-point {
-  position: absolute;
-  width: 12px;
-  height: 12px;
-  left: 0;
-  top: 0;
-  transform-origin: 0 0;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-
-  svg {
-    overflow: visible;
-  }
-
-  &.t-l {
-    cursor: nwse-resize;
-    left: 0;
-    top: 0;
-  }
-  &.t-r {
-    cursor: nesw-resize;
-    left: 100%;
-    top: 0;
-    transform: rotate(90deg);
-  }
-  &.b-l {
-    cursor: nesw-resize;
-    left: 0;
-    top: 100%;
-    transform: rotate(-90deg);
-  }
-  &.b-r {
-    cursor: nwse-resize;
-    left: 100%;
-    top: 100%;
-    transform: rotate(180deg);
-  }
-}
+<template>
+  <div 
+    class="image-clip-handler" 
+    :style="clipWrapperPositionStyle" 
+    v-click-outside="handleClip"
+  >
+    <img 
+      class="bottom-img" 
+      :src="src" 
+      :draggable="false" 
+      alt="" 
+      :style="bottomImgPositionStyle" 
+    />
+
+    <div 
+      class="top-image-content" 
+      :style="{
+        ...topImgWrapperPositionStyle,
+        clipPath,
+      }"
+    >
+      <img 
+        class="top-img" 
+        :src="src" 
+        :draggable="false" 
+        alt="" 
+        :style="topImgPositionStyle" 
+      />
+    </div>
+
+    <div 
+      class="operate" 
+      :style="topImgWrapperPositionStyle" 
+      @mousedown.stop="$event => moveClipRange($event)"
+    >
+      <div 
+        :class="['clip-point', point]" 
+        v-for="point in ['t-l', 't-r', 'b-l', 'b-r']" 
+        :key="point" 
+        @mousedown.stop="$event => scaleClipRange($event, point)"
+      >
+        <SvgWrapper width="12" height="12" fill="#fff" stroke="#666">
+          <path d="M 12 0 L 0 0 L 0 12 L 3 12 L 3 3 L 12 3 L 12 0 Z"></path>
+        </SvgWrapper>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts">
+import { computed, defineComponent, onMounted, onUnmounted, PropType, reactive, ref } from 'vue'
+import { useStore } from 'vuex'
+import { State } from '@/store'
+import { KEYS } from '@/configs/hotkey'
+import { ImageClipData, ImageClipDataRange, ImageClipedEmitData } from '@/types/edit'
+
+type ScaleClipRangeType = 't-l' | 't-r' | 'b-l' | 'b-r'
+
+export default defineComponent({
+  name: 'image-clip-handler',
+  props: {
+    src: {
+      type: String,
+      required: true,
+    },
+    clipData: {
+      type: Object as PropType<ImageClipData>,
+    },
+    clipPath: {
+      type: String,
+      required: true,
+    },
+    width: {
+      type: Number,
+      required: true,
+    },
+    height: {
+      type: Number,
+      required: true,
+    },
+    top: {
+      type: Number,
+      required: true,
+    },
+    left: {
+      type: Number,
+      required: true,
+    },
+  },
+  setup(props, { emit }) {
+    const store = useStore<State>()
+    const canvasScale = computed(() => store.state.canvasScale)
+
+    const topImgWrapperPosition = reactive({
+      top: 0,
+      left: 0,
+      width: 0,
+      height: 0,
+    })
+    const clipWrapperPositionStyle = reactive({
+      top: '0',
+      left: '0',
+    })
+    const isSettingClipRange = ref(false)
+    const currentRange = ref<ImageClipDataRange | null>(null)
+
+    const getClipDataTransformInfo = () => {
+      const [start, end] = props.clipData ? props.clipData.range : [[0, 0], [100, 100]]
+
+      const widthScale = (end[0] - start[0]) / 100
+      const heightScale = (end[1] - start[1]) / 100
+      const left = start[0] / widthScale
+      const top = start[1] / heightScale
+
+      return { widthScale, heightScale, left, top }
+    }
+
+    const imgPosition = computed(() => {
+      const { widthScale, heightScale, left, top } = getClipDataTransformInfo()
+      return {
+        left: -left,
+        top: -top,
+        width: 100 / widthScale,
+        height: 100 / heightScale,
+      }
+    })
+
+    const bottomImgPositionStyle = computed(() => {
+      return {
+        top: imgPosition.value.top + '%',
+        left: imgPosition.value.left + '%',
+        width: imgPosition.value.width + '%',
+        height: imgPosition.value.height + '%',
+      }
+    })
+
+    const topImgWrapperPositionStyle = computed(() => {
+      return {
+        top: topImgWrapperPosition.top + '%',
+        left: topImgWrapperPosition.left + '%',
+        width: topImgWrapperPosition.width + '%',
+        height: topImgWrapperPosition.height + '%',
+      }
+    })
+
+    const topImgPositionStyle = computed(() => {
+      const bottomWidth = imgPosition.value.width
+      const bottomHeight = imgPosition.value.height
+      
+      const topLeft = topImgWrapperPosition.left
+      const topTop = topImgWrapperPosition.top
+      const topWidth = topImgWrapperPosition.width
+      const topHeight = topImgWrapperPosition.height
+      
+      return {
+        left: -topLeft * (100 / topWidth) + '%',
+        top: -topTop * (100 / topHeight) + '%',
+        width: bottomWidth / topWidth * 100 + '%',
+        height: bottomHeight / topHeight * 100 + '%',
+      }
+    })
+
+    const initClipPosition = () => {
+      const { left, top } = getClipDataTransformInfo()
+      topImgWrapperPosition.left = left
+      topImgWrapperPosition.top = top
+      topImgWrapperPosition.width = 100
+      topImgWrapperPosition.height = 100
+      
+      clipWrapperPositionStyle.top = -top + '%'
+      clipWrapperPositionStyle.left = -left + '%'
+    }
+
+    const handleClip = () => {
+      if(isSettingClipRange.value) return
+
+      if(!currentRange.value) {
+        emit('clip', null)
+        return
+      }
+
+      const { left, top } = getClipDataTransformInfo()
+
+      const position = {
+        left: (topImgWrapperPosition.left - left) / 100 * props.width,
+        top: (topImgWrapperPosition.top - top) / 100 * props.height,
+        width: (topImgWrapperPosition.width - 100) / 100 * props.width,
+        height: (topImgWrapperPosition.height - 100) / 100 * props.height,
+      }
+
+      const clipedEmitData: ImageClipedEmitData = {
+        range: currentRange.value,
+        position,
+      }
+      emit('clip', clipedEmitData)
+    }
+
+    const keyboardClip = (e: KeyboardEvent) => {
+      const key = e.key.toUpperCase()
+      if(key === KEYS.ENTER) handleClip()
+    }
+
+    onMounted(() => {
+      initClipPosition()
+      document.addEventListener('keydown', keyboardClip)
+    })
+    onUnmounted(() => {
+      document.removeEventListener('keydown', keyboardClip)
+    })
+
+    const getRange = () => {
+      const retPosition = {
+        left: parseInt(topImgPositionStyle.value.left),
+        top: parseInt(topImgPositionStyle.value.top),
+        width: parseInt(topImgPositionStyle.value.width),
+        height: parseInt(topImgPositionStyle.value.height),
+      }
+
+      const widthScale = 100 / retPosition.width
+      const heightScale = 100 / retPosition.height
+
+      const start: [number, number] = [
+        -retPosition.left * widthScale,
+        -retPosition.top * heightScale,
+      ]
+      const end: [number, number] = [
+        widthScale * 100 + start[0],
+        heightScale * 100 + start[1],
+      ]
+
+      currentRange.value = [start, end]
+    }
+
+    const moveClipRange = (e: MouseEvent) => {
+      isSettingClipRange.value = true
+      let isMouseDown = true
+
+      const startPageX = e.pageX
+      const startPageY = e.pageY
+      const bottomPosition = imgPosition.value
+      const originPositopn = {
+        left: topImgWrapperPosition.left,
+        top: topImgWrapperPosition.top,
+        width: topImgWrapperPosition.width,
+        height: topImgWrapperPosition.height,
+      }
+
+      document.onmousemove = e => {
+        if(!isMouseDown) return
+
+        const currentPageX = e.pageX
+        const currentPageY = e.pageY
+
+        const moveX = (currentPageX - startPageX) / canvasScale.value / props.width * 100
+        const moveY = (currentPageY - startPageY) / canvasScale.value / props.height * 100
+
+        let targetLeft = originPositopn.left + moveX
+        let targetTop = originPositopn.top + moveY
+
+        // 范围限制
+        if(targetLeft < 0) targetLeft = 0
+        else if(targetLeft + originPositopn.width > bottomPosition.width) {
+          targetLeft = bottomPosition.width - originPositopn.width
+        }
+        if(targetTop < 0) targetTop = 0
+        else if(targetTop + originPositopn.height > bottomPosition.height) {
+          targetTop = bottomPosition.height - originPositopn.height
+        }
+        
+        topImgWrapperPosition.left = targetLeft
+        topImgWrapperPosition.top = targetTop
+      }
+
+      document.onmouseup = () => {
+        isMouseDown = false
+        document.onmousemove = null
+        document.onmouseup = null
+
+        getRange()
+
+        setTimeout(() => {
+          isSettingClipRange.value = false
+        }, 0)
+      }
+    }
+
+    const scaleClipRange = (e: MouseEvent, type: ScaleClipRangeType) => {
+      isSettingClipRange.value = true
+      let isMouseDown = true
+
+      const minWidth = 32 / props.width * 100
+      const minHeight = 32 / props.height * 100
+      
+      const startPageX = e.pageX
+      const startPageY = e.pageY
+      const bottomPosition = imgPosition.value
+      const originPositopn = {
+        left: topImgWrapperPosition.left,
+        top: topImgWrapperPosition.top,
+        width: topImgWrapperPosition.width,
+        height: topImgWrapperPosition.height,
+      }
+
+      document.onmousemove = e => {
+        if(!isMouseDown) return
+
+        const currentPageX = e.pageX
+        const currentPageY = e.pageY
+
+        let moveX = (currentPageX - startPageX) / canvasScale.value / props.width * 100
+        let moveY = (currentPageY - startPageY) / canvasScale.value / props.height * 100
+
+        let targetLeft, targetTop, targetWidth, targetHeight
+
+        // 根据不同缩放点,计算目标大小和位置,同时做大小和范围的限制
+        if(type === 't-l') {
+          if(originPositopn.left + moveX < 0) {
+            moveX = -originPositopn.left
+          }
+          if(originPositopn.top + moveY < 0) {
+            moveY = -originPositopn.top
+          }
+          if(originPositopn.width - moveX < minWidth) {
+            moveX = originPositopn.width - minWidth
+          }
+          if(originPositopn.height - moveY < minHeight) {
+            moveY = originPositopn.height - minHeight
+          }
+          targetWidth = originPositopn.width - moveX
+          targetHeight = originPositopn.height - moveY
+          targetLeft = originPositopn.left + moveX
+          targetTop = originPositopn.top + moveY
+        }
+        else if(type === 't-r') {
+          if(originPositopn.left + originPositopn.width + moveX > bottomPosition.width) {
+            moveX = bottomPosition.width - (originPositopn.left + originPositopn.width)
+          }
+          if(originPositopn.top + moveY < 0) {
+            moveY = -originPositopn.top
+          }
+          if(originPositopn.width + moveX < minWidth) {
+            moveX = minWidth - originPositopn.width
+          }
+          if(originPositopn.height - moveY < minHeight) {
+            moveY = originPositopn.height - minHeight
+          }
+          targetWidth = originPositopn.width + moveX
+          targetHeight = originPositopn.height - moveY
+          targetLeft = originPositopn.left
+          targetTop = originPositopn.top + moveY
+        }
+        else if(type === 'b-l') {
+          if(originPositopn.left + moveX < 0) {
+            moveX = -originPositopn.left
+          }
+          if(originPositopn.top + originPositopn.height + moveY > bottomPosition.height) {
+            moveY = bottomPosition.height - (originPositopn.top + originPositopn.height)
+          }
+          if(originPositopn.width - moveX < minWidth) {
+            moveX = originPositopn.width - minWidth
+          }
+          if(originPositopn.height + moveY < minHeight) {
+            moveY = minHeight - originPositopn.height
+          }
+          targetWidth = originPositopn.width - moveX
+          targetHeight = originPositopn.height + moveY
+          targetLeft = originPositopn.left + moveX
+          targetTop = originPositopn.top
+        }
+        else {
+          if(originPositopn.left + originPositopn.width + moveX > bottomPosition.width) {
+            moveX = bottomPosition.width - (originPositopn.left + originPositopn.width)
+          }
+          if(originPositopn.top + originPositopn.height + moveY > bottomPosition.height) {
+            moveY = bottomPosition.height - (originPositopn.top + originPositopn.height)
+          }
+          if(originPositopn.width + moveX < minWidth) {
+            moveX = minWidth - originPositopn.width
+          }
+          if(originPositopn.height + moveY < minHeight) {
+            moveY = minHeight - originPositopn.height
+          }
+          targetWidth = originPositopn.width + moveX
+          targetHeight = originPositopn.height + moveY
+          targetLeft = originPositopn.left
+          targetTop = originPositopn.top
+        }
+        
+        topImgWrapperPosition.left = targetLeft
+        topImgWrapperPosition.top = targetTop
+        topImgWrapperPosition.width = targetWidth
+        topImgWrapperPosition.height = targetHeight
+      }
+
+      document.onmouseup = () => {
+        isMouseDown = false
+        document.onmousemove = null
+        document.onmouseup = null
+
+        getRange()
+
+        setTimeout(() => isSettingClipRange.value = false, 0)
+      }
+    }
+
+    return {
+      clipWrapperPositionStyle,
+      bottomImgPositionStyle,
+      topImgWrapperPositionStyle,
+      topImgPositionStyle,
+      handleClip,
+      moveClipRange,
+      scaleClipRange,
+    }
+  },
+})
+</script>
+
+<style lang="scss" scoped>
+.image-clip-handler {
+  width: 100%;
+  height: 100%;
+  position: relative;
+
+  .bottom-img {
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    opacity: .5;
+  }
+
+  img {
+    width: 100%;
+    height: 100%;
+  }
+
+  .top-image-content {
+    position: absolute;
+    overflow: hidden;
+
+    img {
+      position: absolute;
+    }
+  }
+}
+
+.operate {
+  position: absolute;
+  width: 100%;
+  height: 100%;
+  top: 0;
+  left: 0;
+  cursor: move;
+}
+
+.clip-point {
+  position: absolute;
+  width: 12px;
+  height: 12px;
+  left: 0;
+  top: 0;
+  transform-origin: 0 0;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+
+  svg {
+    overflow: visible;
+  }
+
+  &.t-l {
+    cursor: nwse-resize;
+    left: 0;
+    top: 0;
+  }
+  &.t-r {
+    cursor: nesw-resize;
+    left: 100%;
+    top: 0;
+    transform: rotate(90deg);
+  }
+  &.b-l {
+    cursor: nesw-resize;
+    left: 0;
+    top: 100%;
+    transform: rotate(-90deg);
+  }
+  &.b-r {
+    cursor: nwse-resize;
+    left: 100%;
+    top: 100%;
+    transform: rotate(180deg);
+  }
+}
 </style>

+ 38 - 10
src/views/components/element/ImageElement/index.vue

@@ -1,10 +1,7 @@
 <template>
   <div 
     class="editable-element-image"
-    :class="{
-      'lock': elementInfo.lock,
-      'cliping': clipingImageElementId === elementInfo.id,
-    }"
+    :class="{ 'lock': elementInfo.lock }"
     :style="{
       top: elementInfo.top + 'px',
       left: elementInfo.left + 'px',
@@ -14,8 +11,20 @@
     }"
     @mousedown="$event => handleSelectElement($event)" 
   >
+    <ImageClipHandler
+      v-if="isCliping"
+      :src="elementInfo.src"
+      :clipData="elementInfo.clip"
+      :width="elementInfo.width"
+      :height="elementInfo.height"
+      :top="elementInfo.top"
+      :left="elementInfo.left"
+      :clipPath="clipShape.style"
+      @clip="range => clip(range)"
+    />
     <div 
       class="element-content"
+      v-else
       v-contextmenu="contextmenus"
       :style="{
         filter: shadowStyle ? `drop-shadow(${shadowStyle})` : '',
@@ -64,7 +73,7 @@
 <script lang="ts">
 import { computed, defineComponent, PropType } from 'vue'
 import { useStore } from 'vuex'
-import { State } from '@/store'
+import { MutationTypes, State } from '@/store'
 import { PPTImageElement } from '@/types/slides'
 import { ContextmenuItem } from '@/components/Contextmenu/types'
 import { CLIPPATHS, ClipPathTypes } from '@/configs/imageClip'
@@ -73,7 +82,8 @@ import useElementShadow from '@/views/components/element/hooks/useElementShadow'
 import ImageRectOutline from './ImageRectOutline.vue'
 import ImageEllipseOutline from './ImageEllipseOutline.vue'
 import ImagePolygonOutline from './ImagePolygonOutline.vue'
-
+import ImageClipHandler from './ImageClipHandler.vue'
+import { ImageClipedEmitData } from '@/types/edit'
 
 export default defineComponent({
   name: 'editable-element-image',
@@ -81,6 +91,7 @@ export default defineComponent({
     ImageRectOutline,
     ImageEllipseOutline,
     ImagePolygonOutline,
+    ImageClipHandler,
   },
   props: {
     elementInfo: {
@@ -98,6 +109,7 @@ export default defineComponent({
   setup(props) {
     const store = useStore<State>()
     const clipingImageElementId = computed(() => store.state.clipingImageElementId)
+    const isCliping = computed(() => clipingImageElementId.value === props.elementInfo.id)
 
     const shadow = computed(() => props.elementInfo.shadow)
     const { shadowStyle } = useElementShadow(shadow)
@@ -157,7 +169,27 @@ export default defineComponent({
       return ''
     })
 
+    const clip = (data: ImageClipedEmitData) => {
+      store.commit(MutationTypes.SET_CLIPING_IMAGE_ELEMENT_ID, '')
+      
+      if(!data) return
+
+      const { range, position } = data
+      const originClip = props.elementInfo.clip || {}
+      
+      const _props = {
+        clip: { ...originClip, range },
+        left: props.elementInfo.left + position.left,
+        top: props.elementInfo.top + position.top,
+        width: props.elementInfo.width + position.width,
+        height: props.elementInfo.height + position.height,
+      }
+      store.commit(MutationTypes.UPDATE_ELEMENT, { id: props.elementInfo.id, props: _props })
+    }
+
     return {
+      isCliping,
+      clip,
       clipingImageElementId,
       shadowStyle,
       handleSelectElement,
@@ -177,10 +209,6 @@ export default defineComponent({
   &.lock .element-content {
     cursor: default;
   }
-
-  &.cliping {
-    visibility: hidden;
-  }
 }
 
 .element-content {