添加形状元素的渲染

This commit is contained in:
pipipi-pikachu 2020-12-26 20:42:11 +08:00
parent 665e26c833
commit 8fe7b266f3
11 changed files with 330 additions and 86 deletions

View File

@ -109,7 +109,7 @@ export default () => {
})
}
const createShapeElement = (position: CommonElementPosition, svgCode: string) => {
const createShapeElement = (position: CommonElementPosition, path: string, viewBox: number) => {
const { left, top, width, height } = position
createElement({
...DEFAULT_SHAPE,
@ -119,7 +119,8 @@ export default () => {
top,
width,
height,
svgCode,
viewBox,
path,
})
}

View File

@ -88,6 +88,21 @@ export const slides: Slide[] = [
lock: false,
content: '<div>😀 😐 😶 😜 🔔 ⭐ ⚡ 🔥 👍 💡 🔰 🎀 🎁 🥇 🏅 🏆 🎈 🎉 💎 🚧 ⛔ 📢 ⌛ ⏰ 🕒 🧩 🎵 📎 🔒 🔑 ⛳ 📌 📍 💬 📅 📈 📋 📜 📁 📱 💻 💾 🌏 🚚 🚡 🚢💧 🌐 🧭 💰 💳 🛒</div>',
},
{
id: 'xxx7',
type: 'shape',
left: 130,
top: 50,
width: 150,
height: 150,
rotate: 0,
fill: '#eebc29',
opacity: 0.9,
fixedRatio: false,
lock: false,
viewBox: 1024,
path: 'M721.35111111 475.59111111H302.64888889c-5.00622222 0-9.10222222 4.096-9.10222222 9.10222222v54.61333334c0 5.00622222 4.096 9.10222222 9.10222222 9.10222222h418.70222222c5.00622222 0 9.10222222-4.096 9.10222222-9.10222222v-54.61333334c0-5.00622222-4.096-9.10222222-9.10222222-9.10222222z M512 2.27555555C230.51377778 2.27555555 2.27555555 230.51377778 2.27555555 512s228.23822222 509.72444445 509.72444445 509.72444445 509.72444445-228.23822222 509.72444445-509.72444445S793.48622222 2.27555555 512 2.27555555z m0 932.97777778c-233.69955555 0-423.25333333-189.55377778-423.25333333-423.25333333s189.55377778-423.25333333 423.25333333-423.25333333 423.25333333 189.55377778 423.25333333 423.25333333-189.55377778 423.25333333-423.25333333 423.25333333z',
}
],
animations: [
{

View File

@ -70,7 +70,8 @@ export interface PPTShapeElement extends PPTElementBaseProps {
type: 'shape';
width: number;
height: number;
svgCode: string;
viewBox: number;
path: string;
fixedRatio: boolean;
fill: string;
rotate?: number;

View File

@ -30,6 +30,7 @@ import { ElementOrderCommands, ElementAlignCommands } from '@/types/edit'
import ImageElement from '@/views/components/element/ImageElement/index.vue'
import TextElement from '@/views/components/element/TextElement/index.vue'
import ShapeElement from '@/views/components/element/ShapeElement/index.vue'
export default defineComponent({
name: 'editable-element',
@ -56,6 +57,7 @@ export default defineComponent({
const elementTypeMap = {
'image': ImageElement,
'text': TextElement,
'shape': ShapeElement,
}
return elementTypeMap[props.elementInfo.type] || null
})

View File

@ -11,15 +11,7 @@
:clipPath="clipShape.style"
@clip="range => clip(range)"
/>
<div
class="image-element-operate"
v-else
:class="{
'selected': isSelected,
'multi-select': isMultiSelect && isSelected,
'active': isActive,
}"
>
<div class="image-element-operate" v-else>
<BorderLine
class="operate-border-line"
v-for="line in borderLines"
@ -72,14 +64,6 @@ export default defineComponent({
type: Object as PropType<PPTImageElement>,
required: true,
},
isSelected: {
type: Boolean,
required: true,
},
isActive: {
type: Boolean,
required: true,
},
isActiveGroupElement: {
type: Boolean,
required: true,
@ -136,26 +120,4 @@ export default defineComponent({
}
},
})
</script>
<style lang="scss" scoped>
.image-element-operate {
&.selected {
.operate-border-line,
.operate-resize-handler,
.operate-rotate-handler {
display: block;
}
}
&.multi-select:not(.active) .operate-border-line {
border-color: rgba($color: $themeColor, $alpha: .3);
}
.operate-border-line,
.operate-resize-handler,
.operate-rotate-handler {
display: none;
}
}
</style>
</script>

View File

@ -0,0 +1,85 @@
<template>
<div class="text-element-operate">
<BorderLine
class="operate-border-line"
v-for="line in borderLines"
:key="line.type"
:type="line.type"
:style="line.style"
/>
<template v-if="!elementInfo.lock && (isActiveGroupElement || !isMultiSelect)">
<ResizeHandler
class="operate-resize-handler"
v-for="point in resizeHandlers"
:key="point.direction"
:type="point.direction"
:style="point.style"
@mousedown.stop="$event => scaleElement($event, elementInfo, point.direction)"
/>
<RotateHandler
class="operate-rotate-handler"
:style="{ left: scaleWidth / 2 + 'px' }"
@mousedown.stop="rotateElement(elementInfo)"
/>
</template>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, PropType } from 'vue'
import { useStore } from 'vuex'
import { State } from '@/store'
import { PPTShapeElement } from '@/types/slides'
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'
export default defineComponent({
name: 'text-element-operate',
components: {
RotateHandler,
ResizeHandler,
BorderLine,
},
props: {
elementInfo: {
type: Object as PropType<PPTShapeElement>,
required: true,
},
isActiveGroupElement: {
type: Boolean,
required: true,
},
isMultiSelect: {
type: Boolean,
required: true,
},
rotateElement: {
type: Function as PropType<(element: PPTShapeElement) => void>,
required: true,
},
scaleElement: {
type: Function as PropType<(e: MouseEvent, element: PPTShapeElement, command: OperateResizeHandler) => void>,
required: true,
},
},
setup(props) {
const store = useStore<State>()
const canvasScale = computed(() => store.state.canvasScale)
const scaleWidth = computed(() => props.elementInfo.width * canvasScale.value)
const scaleHeight = computed(() => props.elementInfo.height * canvasScale.value)
const { resizeHandlers, borderLines } = useCommonOperate(scaleWidth, scaleHeight)
return {
scaleWidth,
resizeHandlers,
borderLines,
}
},
})
</script>

View File

@ -1,12 +1,5 @@
<template>
<div
class="text-element-operate"
:class="{
'selected': isSelected,
'multi-select': isMultiSelect && isSelected,
'active': isActive,
}"
>
<div class="text-element-operate">
<BorderLine
class="operate-border-line"
v-for="line in borderLines"
@ -57,14 +50,6 @@ export default defineComponent({
type: Object as PropType<PPTTextElement>,
required: true,
},
isSelected: {
type: Boolean,
required: true,
},
isActive: {
type: Boolean,
required: true,
},
isActiveGroupElement: {
type: Boolean,
required: true,
@ -98,26 +83,4 @@ export default defineComponent({
}
},
})
</script>
<style lang="scss" scoped>
.text-element-operate {
&.selected {
.operate-border-line,
.operate-resize-handler,
.operate-rotate-handler {
display: block;
}
}
&.multi-select:not(.active) .operate-border-line {
border-color: rgba($color: $themeColor, $alpha: .3);
}
.operate-border-line,
.operate-resize-handler,
.operate-rotate-handler {
display: none;
}
}
</style>
</script>

View File

@ -1,21 +1,20 @@
<template>
<div
class="operate"
:class="{ 'multi-select': isMultiSelect && !isActive }"
v-if="isSelected"
:style="{
top: elementInfo.top * canvasScale + 'px',
left: elementInfo.left * canvasScale + 'px',
transform: `rotate(${elementInfo.rotate}deg)`,
'transform-origin': `${elementInfo.width * canvasScale / 2}px ${elementInfo.height * canvasScale / 2}px`,
transformOrigin: `${elementInfo.width * canvasScale / 2}px ${elementInfo.height * canvasScale / 2}px`,
}"
>
<component
:is="currentOperateComponent"
:elementInfo="elementInfo"
:isSelected="isSelected"
:isActive="isActive"
:isActiveGroupElement="isActiveGroupElement"
:isMultiSelect="isMultiSelect"
:animationIndex="elementIndexInAnimation"
:rotateElement="rotateElement"
:scaleElement="scaleElement"
></component>
@ -38,6 +37,7 @@ import { OperateResizeHandler } from '@/types/edit'
import ImageElementOperate from './ImageElementOperate.vue'
import TextElementOperate from './TextElementOperate.vue'
import ShapeElementOperate from './ShapeElementOperate.vue'
export default defineComponent({
name: 'operate',
@ -81,6 +81,7 @@ export default defineComponent({
const elementTypeMap = {
'image': ImageElementOperate,
'text': TextElementOperate,
'shape': ShapeElementOperate,
}
return elementTypeMap[props.elementInfo.type] || null
})
@ -105,6 +106,10 @@ export default defineComponent({
position: absolute;
z-index: 100;
user-select: none;
&.multi-select {
opacity: .3;
}
}
.animation-index {
position: absolute;

View File

@ -16,6 +16,7 @@ import { PPTElement } from '@/types/slides'
import BaseImageElement from '@/views/components/element/ImageElement/BaseImageElement.vue'
import BaseTextElement from '@/views/components/element/TextElement/BaseTextElement.vue'
import BaseShapeElement from '@/views/components/element/ShapeElement/BaseShapeElement.vue'
export default defineComponent({
name: 'base-element',
@ -34,6 +35,7 @@ export default defineComponent({
const elementTypeMap = {
'image': BaseImageElement,
'text': BaseTextElement,
'shape': BaseShapeElement,
}
return elementTypeMap[props.elementInfo.type] || null
})

View File

@ -0,0 +1,92 @@
<template>
<div class="base-element-shape"
:style="{
top: elementInfo.top + 'px',
left: elementInfo.left + 'px',
width: elementInfo.width + 'px',
transform: `rotate(${elementInfo.rotate}deg)`,
}"
>
<div
class="element-content"
:style="{
opacity: elementInfo.opacity,
filter: shadowStyle ? `drop-shadow(${shadowStyle})` : '',
}"
>
<SvgWrapper overflow="visible"
:width="elementInfo.width"
:height="elementInfo.height"
>
<g
:transform="`scale(${elementInfo.width / elementInfo.viewBox}, ${elementInfo.height / elementInfo.viewBox}) translate(0,0) matrix(1,0,0,1,0,0)`"
>
<path
vector-effect="non-scaling-stroke"
stroke-linecap="butt"
stroke-miterlimit="8"
stroke-linejoin=""
:d="elementInfo.path"
:fill="elementInfo.fill"
:stroke="outlineColor"
:stroke-width="outlineWidth"
:stroke-dasharray="outlineStyle === 'dashed' ? '10 5' : '0 0'"
></path>
</g>
</SvgWrapper>
</div>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, PropType } from 'vue'
import { PPTShapeElement } from '@/types/slides'
import useElementOutline from '@/views/components/element/hooks/useElementOutline'
import useElementShadow from '@/views/components/element/hooks/useElementShadow'
import SvgWrapper from '@/components/SvgWrapper.vue'
export default defineComponent({
name: 'base-element-shape',
components: {
SvgWrapper,
},
props: {
elementInfo: {
type: Object as PropType<PPTShapeElement>,
required: true,
},
},
setup(props) {
const outline = computed(() => props.elementInfo.outline)
const { outlineWidth, outlineStyle, outlineColor } = useElementOutline(outline)
const shadow = computed(() => props.elementInfo.shadow)
const { shadowStyle } = useElementShadow(shadow)
return {
shadowStyle,
outlineWidth,
outlineStyle,
outlineColor,
}
},
})
</script>
<style lang="scss" scoped>
.base-element-shape {
position: absolute;
}
.element-content {
width: 100%;
height: 100%;
position: relative;
svg {
transform-origin: 0 0;
overflow: visible;
}
}
</style>

View File

@ -0,0 +1,116 @@
<template>
<div class="editable-element-shape"
:class="{ 'lock': elementInfo.lock }"
:style="{
top: elementInfo.top + 'px',
left: elementInfo.left + 'px',
width: elementInfo.width + 'px',
transform: `rotate(${elementInfo.rotate}deg)`,
}"
@mousedown="$event => handleSelectElement($event)"
>
<div
class="element-content"
v-contextmenu="contextmenus"
:style="{
opacity: elementInfo.opacity,
filter: shadowStyle ? `drop-shadow(${shadowStyle})` : '',
}"
>
<SvgWrapper overflow="visible"
:width="elementInfo.width"
:height="elementInfo.height"
>
<g
:transform="`scale(${elementInfo.width / elementInfo.viewBox}, ${elementInfo.height / elementInfo.viewBox}) translate(0,0) matrix(1,0,0,1,0,0)`"
>
<path
vector-effect="non-scaling-stroke"
stroke-linecap="butt"
stroke-miterlimit="8"
stroke-linejoin=""
:d="elementInfo.path"
:fill="elementInfo.fill"
:stroke="outlineColor"
:stroke-width="outlineWidth"
:stroke-dasharray="outlineStyle === 'dashed' ? '10 5' : '0 0'"
></path>
</g>
</SvgWrapper>
</div>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, PropType } from 'vue'
import { PPTShapeElement } from '@/types/slides'
import { ContextmenuItem } from '@/components/Contextmenu/types'
import useElementOutline from '@/views/components/element/hooks/useElementOutline'
import useElementShadow from '@/views/components/element/hooks/useElementShadow'
import SvgWrapper from '@/components/SvgWrapper.vue'
export default defineComponent({
name: 'editable-element-shape',
components: {
SvgWrapper,
},
props: {
elementInfo: {
type: Object as PropType<PPTShapeElement>,
required: true,
},
selectElement: {
type: Function as PropType<(e: MouseEvent, element: PPTShapeElement, canMove?: boolean) => void>,
required: true,
},
contextmenus: {
type: Function as PropType<() => ContextmenuItem[]>,
},
},
setup(props) {
const handleSelectElement = (e: MouseEvent) => {
if(props.elementInfo.lock) return
e.stopPropagation()
props.selectElement(e, props.elementInfo)
}
const outline = computed(() => props.elementInfo.outline)
const { outlineWidth, outlineStyle, outlineColor } = useElementOutline(outline)
const shadow = computed(() => props.elementInfo.shadow)
const { shadowStyle } = useElementShadow(shadow)
return {
handleSelectElement,
shadowStyle,
outlineWidth,
outlineStyle,
outlineColor,
}
},
})
</script>
<style lang="scss" scoped>
.editable-element-shape {
position: absolute;
cursor: move;
&.lock .element-content {
cursor: default;
}
}
.element-content {
width: 100%;
height: 100%;
position: relative;
svg {
transform-origin: 0 0;
overflow: visible;
}
}
</style>