mirror of
https://github.com/pipipi-pikachu/PPTist.git
synced 2025-04-15 02:20:00 +08:00
feat: 支持图片着色(蒙版)
This commit is contained in:
parent
06edd83960
commit
52e2142434
@ -211,6 +211,18 @@ export interface ImageElementClip {
|
|||||||
shape: string
|
shape: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图片蒙版
|
||||||
|
*
|
||||||
|
* color: 蒙版颜色
|
||||||
|
*
|
||||||
|
* opacity: 蒙版透明度
|
||||||
|
*/
|
||||||
|
export interface ImageColorElementMask {
|
||||||
|
color: string
|
||||||
|
opacity: number
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 图片元素
|
* 图片元素
|
||||||
*
|
*
|
||||||
@ -242,6 +254,7 @@ export interface PPTImageElement extends PPTBaseElement {
|
|||||||
flipH?: boolean
|
flipH?: boolean
|
||||||
flipV?: boolean
|
flipV?: boolean
|
||||||
shadow?: PPTElementShadow
|
shadow?: PPTElementShadow
|
||||||
|
colorMask?: ImageColorElementMask
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -41,6 +41,8 @@
|
|||||||
</Popover>
|
</Popover>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
<ElementColorMask />
|
||||||
<Divider />
|
<Divider />
|
||||||
<ElementFilter />
|
<ElementFilter />
|
||||||
<Divider />
|
<Divider />
|
||||||
@ -70,6 +72,7 @@ import ElementOutline from '../common/ElementOutline.vue'
|
|||||||
import ElementShadow from '../common/ElementShadow.vue'
|
import ElementShadow from '../common/ElementShadow.vue'
|
||||||
import ElementFlip from '../common/ElementFlip.vue'
|
import ElementFlip from '../common/ElementFlip.vue'
|
||||||
import ElementFilter from '../common/ElementFilter.vue'
|
import ElementFilter from '../common/ElementFilter.vue'
|
||||||
|
import ElementColorMask from '../common/ElementColorMask.vue'
|
||||||
|
|
||||||
const shapeClipPathOptions = CLIPPATHS
|
const shapeClipPathOptions = CLIPPATHS
|
||||||
const ratioClipOptions = [
|
const ratioClipOptions = [
|
||||||
@ -235,7 +238,7 @@ const resetImage = () => {
|
|||||||
|
|
||||||
slidesStore.removeElementProps({
|
slidesStore.removeElementProps({
|
||||||
id: handleElementId.value,
|
id: handleElementId.value,
|
||||||
propName: ['clip', 'outline', 'flip', 'shadow', 'filters'],
|
propName: ['clip', 'outline', 'flip', 'shadow', 'filters', 'colorMask'],
|
||||||
})
|
})
|
||||||
addHistorySnapshot()
|
addHistorySnapshot()
|
||||||
}
|
}
|
||||||
|
100
src/views/Editor/Toolbar/common/ElementColorMask.vue
Normal file
100
src/views/Editor/Toolbar/common/ElementColorMask.vue
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
<template>
|
||||||
|
<div class="element-color-mask">
|
||||||
|
<div class="row">
|
||||||
|
<div style="flex: 1;">重新着色(蒙版):</div>
|
||||||
|
<div class="switch-wrapper" style="flex: 1;">
|
||||||
|
<Switch
|
||||||
|
:checked="hasColorMask"
|
||||||
|
@change="checked => toggleColorMask(checked as boolean)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<template v-if="hasColorMask">
|
||||||
|
<div class="row" style="margin-top: 15px;">
|
||||||
|
<div style="flex: 2;">蒙版颜色:</div>
|
||||||
|
<Popover trigger="click">
|
||||||
|
<template #content>
|
||||||
|
<ColorPicker
|
||||||
|
:modelValue="colorMask.color"
|
||||||
|
@update:modelValue="value => updateColorMask({ color: value })"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<ColorButton :color="colorMask.color" style="flex: 3;" />
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div style="flex: 2;">不透明度:</div>
|
||||||
|
<Slider
|
||||||
|
class="opacity-slider"
|
||||||
|
:max="1"
|
||||||
|
:min="0"
|
||||||
|
:step="0.05"
|
||||||
|
:value="colorMask.opacity"
|
||||||
|
@change="value => updateColorMask({ opacity: value as number })"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, watch } from 'vue'
|
||||||
|
import { storeToRefs } from 'pinia'
|
||||||
|
import { useMainStore, useSlidesStore } from '@/store'
|
||||||
|
import { ImageColorElementMask } from '@/types/slides'
|
||||||
|
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
|
||||||
|
|
||||||
|
import ColorButton from './ColorButton.vue'
|
||||||
|
|
||||||
|
const defaultColorMask = { color: 'transparent', opacity: 0.3 }
|
||||||
|
|
||||||
|
const slidesStore = useSlidesStore()
|
||||||
|
const { handleElement, handleElementId } = storeToRefs(useMainStore())
|
||||||
|
|
||||||
|
const colorMask = ref<ImageColorElementMask>(defaultColorMask)
|
||||||
|
const hasColorMask = ref(false)
|
||||||
|
|
||||||
|
const { addHistorySnapshot } = useHistorySnapshot()
|
||||||
|
|
||||||
|
watch(handleElement, () => {
|
||||||
|
if (!handleElement.value || handleElement.value.type !== 'image') return
|
||||||
|
|
||||||
|
if (handleElement.value.colorMask) {
|
||||||
|
colorMask.value = handleElement.value.colorMask
|
||||||
|
hasColorMask.value = true
|
||||||
|
}
|
||||||
|
else hasColorMask.value = false
|
||||||
|
}, { deep: true, immediate: true })
|
||||||
|
|
||||||
|
const toggleColorMask = (checked: boolean) => {
|
||||||
|
if (!handleElement.value) return
|
||||||
|
if (checked) {
|
||||||
|
slidesStore.updateElement({ id: handleElement.value.id, props: { colorMask: defaultColorMask } })
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
slidesStore.removeElementProps({ id: handleElement.value.id, propName: 'colorMask' })
|
||||||
|
}
|
||||||
|
addHistorySnapshot()
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateColorMask = (colorMaskProp: Partial<ImageColorElementMask>) => {
|
||||||
|
const newColorMask = { ...colorMask.value, ...colorMaskProp }
|
||||||
|
slidesStore.updateElement({ id: handleElementId.value, props: { colorMask: newColorMask } })
|
||||||
|
addHistorySnapshot()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.row {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.switch-wrapper {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.opacity-slider {
|
||||||
|
flex: 3;
|
||||||
|
}
|
||||||
|
</style>
|
@ -34,6 +34,13 @@
|
|||||||
}"
|
}"
|
||||||
alt=""
|
alt=""
|
||||||
/>
|
/>
|
||||||
|
<div class="color-mask"
|
||||||
|
v-if="elementInfo.colorMask"
|
||||||
|
:style="{
|
||||||
|
backgroundColor: elementInfo.colorMask.color,
|
||||||
|
opacity: elementInfo.colorMask.opacity,
|
||||||
|
}"
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -95,4 +102,11 @@ const { filter } = useFilter(filters)
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.color-mask {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -52,6 +52,13 @@
|
|||||||
@dragstart.prevent
|
@dragstart.prevent
|
||||||
alt=""
|
alt=""
|
||||||
/>
|
/>
|
||||||
|
<div class="color-mask"
|
||||||
|
v-if="elementInfo.colorMask"
|
||||||
|
:style="{
|
||||||
|
backgroundColor: elementInfo.colorMask.color,
|
||||||
|
opacity: elementInfo.colorMask.opacity,
|
||||||
|
}"
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -185,4 +192,11 @@ const handleClip = (data: ImageClipedEmitData | null) => {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.color-mask {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user