Browse Source

字幕初版可拖拽

dev
congzc 1 year ago
parent
commit
a4db850dd0
5 changed files with 2261 additions and 435 deletions
  1. +1
    -1
      src/layout/index.vue
  2. +25
    -4
      src/types/components.d.ts
  3. +1999
    -246
      src/views/createVideo/index copy.vue
  4. +164
    -68
      src/views/createVideo/index.vue
  5. +72
    -116
      src/views/moveImg/index.vue

+ 1
- 1
src/layout/index.vue View File

@@ -25,7 +25,7 @@
</div>
{{ $t("myCreating.title") }}
</div>
<!-- 测试功能页 -->
<!-- 测试功能页 v-if="false"-->
<div
v-if="false"
class="myRouter-item"


+ 25
- 4
src/types/components.d.ts View File

@@ -8,10 +8,13 @@ export {}
declare module '@vue/runtime-core' {
export interface GlobalComponents {
Breadcrumb: typeof import('./../components/Breadcrumb/index.vue')['default']
ElAlert: typeof import('element-plus/es')['ElAlert']
ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
ElButton: typeof import('element-plus/es')['ElButton']
ElCard: typeof import('element-plus/es')['ElCard']
ElCol: typeof import('element-plus/es')['ElCol']
ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
ElDialog: typeof import('element-plus/es')['ElDialog']
ElDivider: typeof import('element-plus/es')['ElDivider']
ElDropdown: typeof import('element-plus/es')['ElDropdown']
@@ -21,35 +24,54 @@ declare module '@vue/runtime-core' {
ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElIcon: typeof import('element-plus/es')['ElIcon']
ElInput: typeof import('element-plus/es')['ElInput']
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
ElLink: typeof import('element-plus/es')['ElLink']
ElOption: typeof import('element-plus/es')['ElOption']
ElPagination: typeof import('element-plus/es')['ElPagination']
ElPopover: typeof import('element-plus/es')['ElPopover']
ElRadio: typeof import('element-plus/es')['ElRadio']
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElRow: typeof import('element-plus/es')['ElRow']
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
ElSelect: typeof import('element-plus/es')['ElSelect']
ElSwitch: typeof import('element-plus/es')['ElSwitch']
ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElTabPane: typeof import('element-plus/es')['ElTabPane']
ElTabs: typeof import('element-plus/es')['ElTabs']
ElTag: typeof import('element-plus/es')['ElTag']
ElTooltip: typeof import('element-plus/es')['ElTooltip']
ElTree: typeof import('element-plus/es')['ElTree']
ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect']
ElUpload: typeof import('element-plus/es')['ElUpload']
GithubCorner: typeof import('./../components/GithubCorner/index.vue')['default']
Hamburger: typeof import('./../components/Hamburger/index.vue')['default']
IconSelect: typeof import('./../components/IconSelect/index.vue')['default']
IEpCaretBottom: typeof import('~icons/ep/caret-bottom')['default']
IEpCaretTop: typeof import('~icons/ep/caret-top')['default']
IEpClose: typeof import('~icons/ep/close')['default']
IEpCloseBold: typeof import('~icons/ep/close-bold')['default']
IEpCollection: typeof import('~icons/ep/collection')['default']
IEpDelete: typeof import('~icons/ep/delete')['default']
IEpDocumentAdd: typeof import('~icons/ep/document-add')['default']
IEpDownload: typeof import('~icons/ep/download')['default']
IEpEdit: typeof import('~icons/ep/edit')['default']
IEpFolder: typeof import('~icons/ep/folder')['default']
IEpFolderAdd: typeof import('~icons/ep/folder-add')['default']
IEpGoods: typeof import('~icons/ep/goods')['default']
IEpHeadset: typeof import('~icons/ep/headset')['default']
IEpLoading: typeof import('~icons/ep/loading')['default']
IEpLock: typeof import('~icons/ep/lock')['default']
IEpMenu: typeof import('~icons/ep/menu')['default']
IEpMic: typeof import('~icons/ep/mic')['default']
IEpPlus: typeof import('~icons/ep/plus')['default']
IEpPosition: typeof import('~icons/ep/position')['default']
IEpRefresh: typeof import('~icons/ep/refresh')['default']
IEpRefreshLeft: typeof import('~icons/ep/refresh-left')['default']
IEpSearch: typeof import('~icons/ep/search')['default']
IEpSetting: typeof import('~icons/ep/setting')['default']
IEpSwitchButton: typeof import('~icons/ep/switch-button')['default']
IEpTop: typeof import('~icons/ep/top')['default']
IEpUploadFilled: typeof import('~icons/ep/upload-filled')['default']
IEpVideoCamera: typeof import('~icons/ep/video-camera')['default']
IEpVideoPause: typeof import('~icons/ep/video-pause')['default']
IEpVideoPlay: typeof import('~icons/ep/video-play')['default']
LangSelect: typeof import('./../components/LangSelect/index.vue')['default']
@@ -64,10 +86,9 @@ declare module '@vue/runtime-core' {
SizeSelect: typeof import('./../components/SizeSelect/index.vue')['default']
SvgIcon: typeof import('./../components/SvgIcon/index.vue')['default']
WangEditor: typeof import('./../components/WangEditor/index.vue')['default']
Websocket: typeof import('./../components/websocket.vue')['default']
Websoket: typeof import('./../components/websoket.vue')['default']
}
export interface ComponentCustomProperties {
vInfiniteScroll: typeof import('element-plus/es')['ElInfiniteScroll']
vLoading: typeof import('element-plus/es')['ElLoadingDirective']
}
}

+ 1999
- 246
src/views/createVideo/index copy.vue
File diff suppressed because it is too large
View File


+ 164
- 68
src/views/createVideo/index.vue View File

@@ -18,18 +18,42 @@
:placeholder="$t('createVideo.typeTitle')"
/>
<el-icon class="icon-edit"><i-ep-edit /></el-icon>
<!-- 编辑字幕框 -->
</div>
<div v-if="imgUrl&&SubtitlesSwitch" class="editSubtitlesBox ">
<div class="item">X:&nbsp;<el-input
v-model="SubtitlesTop"
width="30"
/></div>
<div class="item">Y:&nbsp;<el-input
v-model="SubtitlesLeft"
width="30"
/></div>
<div class="item">Size:&nbsp;<el-input
v-model="SubtitlesSize"
width="30"
/></div>
<div class="item">Color:&nbsp;<el-color-picker v-model="SubtitlesColor" show-alpha /></div>
<div class="item">BGC:&nbsp;<el-color-picker v-model="SubtitlesBgc" show-alpha /></div>
</div>
</el-row>
<!-- 中间展示大图 -->
<div class="bigImg inmiddle">
<div v-if="false" class="canvasBox">
<canvas id="myCanvas" ref="myCanvasRef" >
</canvas>
</div>
<img id="canvasImg" ref="canvasImgRef" :src="imgUrl" alt="" class="inmiddle" />
<div ref="canvasImgBox" class="bigImg inmiddle">
<img id="canvasImg" :src="imgUrl" alt="" class="" />
<div ref="SubtitlesBox" @mousedown="handleDrag" :style="SubtitlesStyle" v-if="SubtitlesSwitch" class="SubtitlesBox">请在文案区编辑您的正文
<div class="stretchBox box1"></div>
<div class="stretchBox box2"></div>
<div class="stretchBox box3"></div>
<div class="stretchBox box4"></div>
<div class="stretchBox box5"></div>
<div class="stretchBox box6"></div>
<div class="stretchBox box7"></div>
<div class="stretchBox box8"></div>
</div>

</div>
<!-- 字幕开关 -->
<div v-if="false" class="SubtitlesSwitch">
<div v-if="imgUrl" class="SubtitlesSwitch">
<p>{{ $t('createVideo.subtitles') }}</p>
<el-switch v-model="SubtitlesSwitch" />
</div>
@@ -1915,42 +1939,49 @@ function copyAnswerContent() {
//#endregion ----------------------------

//#region 字幕部分
const canvasImgBox=ref(null) //图片外盒子dom
const SubtitlesBox=ref(null) //字幕盒子dom
const SubtitlesSwitch=ref(false) //字幕开关
const canvas = ref(null); //canvas对象
const myCanvasRef = ref(null); //画布dom
const canvasImgRef = ref(null); //图片dom
const imgWidth=ref(null)
const imgHeight=ref(null)
const myText = ref("hello world");
const iText = new fabric.IText(myText.value, {
left: 100,
top: 100,
});
function setCanvasText() {
canvas.value = new fabric.Canvas("myCanvas",{
width:imgWidth.value,
height:imgHeight.value,
});
iText.on("mouseup", function () {
console.log(iText, "iText");
console.log(iText.angle.toFixed(0), "angle");
console.log(iText.left.toFixed(0), "left");
console.log(iText.top.toFixed(0), "top");
console.log(iText.width.toFixed(0), "width");
console.log(iText.height.toFixed(0), "height");
});
canvas.value.add(iText);
}
const SubtitlesTop=ref(0) //字幕Y轴
const SubtitlesLeft=ref(0) //字幕X轴
const SubtitlesSize=ref(18) //字幕字号
const SubtitlesColor=ref('rgba(0, 0, 0, 1)') //字幕字色
const SubtitlesBgc=ref('rgba(255, 255, 255,1 )') //字幕背景色
// 计算属性来计算盒子位置
const SubtitlesStyle = computed(() => {
return {
top: SubtitlesTop.value + 'px',
left: SubtitlesLeft.value + 'px',
fontSize: SubtitlesSize.value+ 'px',
color:SubtitlesColor.value,
backgroundColor: SubtitlesBgc.value,
};
});
// 拖动盒子
const handleDrag = (event) => {
event.preventDefault();
const boxElement = canvasImgBox.value;
const imageElement = SubtitlesBox.value;
const offsetX = event.clientX - imageElement.offsetLeft;
const offsetY = event.clientY - imageElement.offsetTop;
const onMouseMove = (event) => {
const newX = event.clientX - offsetX;
const newY = event.clientY - offsetY;
SubtitlesLeft.value= newX;
SubtitlesTop.value = newY;
};
const onMouseUp = () => {
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
};
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
};
// 监听字幕显示
watch(
() => SubtitlesSwitch,
async (newValue, oldValue) => {
if(SubtitlesSwitch.value) {
setCanvasText()
} else {
canvas.value.remove(iText);
canvas.value=null;
}

},
{ deep: true }
);
@@ -1958,11 +1989,8 @@ watch(
watch(
() => imgUrl.value,
async (newValue, oldValue) => {
await nextTick()
if(canvas.value) {
canvas.value.width=imgWidth.value,
canvas.value.height=imgHeight.value
}
await nextTick()
// console.log(getComputedStyle(document.getElementById("canvasImg"),null));
},
{ deep: true }
);
@@ -2013,7 +2041,7 @@ onMounted(async () => {
}
});
createWS(); //创建websocket
});
//#endregion ----------------------------

@@ -2067,6 +2095,34 @@ onUnmounted(() => {
height: 67%;
// height: calc(70vh - 80px );
margin-bottom: 20px;
.editSubtitlesBox{
display: flex;
justify-content: flex-start;
padding-right: 80px;
margin-left: 10px;
margin-right: 80px;
border-radius: 20px;
flex: 1;
line-height: 35px;
background-color: #f1f2f6;
font-size: 16px;
.item {
display: flex;
align-items: center;
margin-left: 10px;
.el-input {
width: 60px;
height: 25px;
}
}
:deep(.el-input__wrapper) {
border: none;
border: 2px solid #000;
border-radius: 5px;
box-shadow: none;

}
}
.uploadBtn {
position: relative;
padding: 10px 15px;
@@ -2086,34 +2142,74 @@ onUnmounted(() => {
width: 50%;
height: 88%;
margin-top: 20px;
.canvasBox {
.SubtitlesBox {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: skyblue;
opacity: 0.5;
// z-index: 9;
.canvas-container {
// background-color: red;
// z-index: 9;
// width: 100%!important;
// height: 100%!important;
// #myCanvas {
// width: 100%!important;
// height: 100%!important;
// }
// .upper-canvas {
// width: 100%!important;
// height: 100%!important;
// }
user-select: none;
width: 300px;
padding: 5px 5px;
border: 2px solid #00ff00;
text-align: center;
background-color: #fff;
cursor: move;
.stretchBox {
position: absolute;
width: 10px;
height: 10px;
background-color: #fff;
}
.box1 {
top: -5px;
left: -5px;
cursor: nwse-resize;
}
.box2 {
top: -5px;
left: 50%;
transform: translateX(-50%);
cursor: ns-resize;
}
.box3 {
top: -5px;
left:100%;
transform: translateX(-5px);
cursor: nesw-resize;
}
.box4 {
top: 50%;
left: -5px;
transform: translateY(-5px);
cursor: ew-resize;
}
.box5 {
top: 50%;
left:100%;
transform: translate(-5px,-5px);
cursor: ew-resize;
}
.box6 {
top: 100%;
left: -5px;
transform: translateY(-5px);
cursor: nesw-resize;
}
.box7 {
top: 100%;
left: 50%;
transform: translate(-5px,-5px);
cursor: ns-resize;
}
.box8 {
top: 100%;
left: 100%;
transform: translate(-5px,-5px);
cursor: nwse-resize;
}
}
img {
// position: absolute;
// left: 0;
// top: 0;
position: absolute;
left: 0;
top: 0;
max-width: 100%;
max-height: 100%;
border-radius: 15px;


+ 72
- 116
src/views/moveImg/index.vue View File

@@ -1,136 +1,92 @@
<template>
<div class="page">
<div class="canvasBox">
<canvas id="myCanvas" width="960" height="540"> </canvas>
<img
style="display: none; width: 200px; height: 200px"
id="bjImg"
src="../../assets/img/star.png"
alt=""
/>
<div>
<img src="../../assets/img/trueImg.png" ref="image" />
<div
class="box"
ref="box"
:style="{ top: boxPosition.top + 'px', left: boxPosition.left + 'px' }"
@mousedown="handleDrag"
>
<!-- 这里可以放置盒子的内容 -->
</div>
<div class="container">
<div class="box red"></div>
<div class="box white"></div>
<div class="box green"></div>
<div class="resizable-box" v-if="isResizing" @mousedown="handleResize">
字幕
</div>
<button @click="handleRotate">旋转</button>
</div>
</template>

<script setup>
//#region 导入
import { fabric } from "fabric";
import { ref, reactive } from "vue";

//#endregion ---------------------------
const image = ref(null);
const box = ref(null);
const boxPosition = reactive({ top: 0, left: 0 });
const isResizing = ref(false);
const rotationAngle = ref(0);

//#region 画布交互
// let canvas = new fabric.Canvas("myCanvas");
let canvas = null;
function setCanvasText() {
// let imgElement = document.getElementById("bjImg"); //声明我们的图片
const myText = ref("hello world");
const iText = new fabric.IText(myText.value, {
left: 100,
top: 100,
});
canvas.add(iText);
iText.on("mouseup", function () {
console.log(iText, "iText");
console.log(iText.angle.toFixed(0), "angle");
console.log(iText.left.toFixed(0), "left");
console.log(iText.top.toFixed(0), "top");
console.log(iText.width.toFixed(0), "width");
console.log(iText.height.toFixed(0), "height");
});
}
//#endregion ---------------------------
// 盒子位置拖动
const handleDrag = (event) => {
event.preventDefault();
const boxElement = box.value;
const imageElement = image.value;

onMounted(() => {
canvas = new fabric.Canvas("myCanvas");
setCanvasText();
// let imgInstance = new fabric.Image(imgElement, {
// //设置图片位置和样子
// // left: 100,
// // top: 100,
// // width: 400,
// // height: 200,
// // angle: 30, //设置图形顺时针旋转角度
// });
// console.log(imgInstance);
// canvas.add(imgInstance);
});
</script>
const offsetX = event.clientX - imageElement.offsetLeft;
const offsetY = event.clientY - imageElement.offsetTop;

<style lang="scss" scoped>
.page {
position: relative;
padding: 10px;
}
.canvasBox {
// position: absolute;
// top: 50%;
// left: 50%;
// transform: translate(-50%);
width: 100%;
height: 100%;
.canvas-container {
}
background-color: green;
.canvas {
background-color: skyblue;
.bjImg {
position: absolute;
top: 0;
left: 0;
width: 100px;
height: 100px;
z-index: 999;
img {
width: 100px;
height: 100px;
}
}
.people {
}
.other {
}
}
}
</style>
const onMouseMove = (event) => {
const newX = event.clientX - offsetX;
const newY = event.clientY - offsetY;

<style lang="scss" scoped>
.container {
display: flex;
align-items: center;
justify-content: space-between;
width: 80px;
height: 20px;
perspective: 500px;
transition: transform 0.5s;
}
boxPosition.left = newX;
boxPosition.top = newY;
};

.box {
width: 20px;
height: 20px;
transition: transform 0.5s;
transform-style: preserve-3d;
}
const onMouseUp = () => {
document.removeEventListener("mousemove", onMouseMove);
document.removeEventListener("mouseup", onMouseUp);
};

.red {
background-color: red;
}
document.addEventListener("mousemove", onMouseMove);
document.addEventListener("mouseup", onMouseUp);
};

.white {
background-color: white;
transform: translateZ(20px);
}
// 边框拖动
const handleResize = () => {
isResizing.value = true;

const onMouseMove = (event) => {
// 处理拉伸逻辑
};

const onMouseUp = () => {
isResizing.value = false;
document.removeEventListener("mousemove", onMouseMove);
document.removeEventListener("mouseup", onMouseUp);
};

.green {
background-color: green;
transform: translateZ(-20px);
document.addEventListener("mousemove", onMouseMove);
document.addEventListener("mouseup", onMouseUp);
};

// 图片旋转
const handleRotate = () => {
rotationAngle.value += 90;
};
</script>

<style>
.box {
position: absolute;
width: 100px;
height: 100px;
background-color: rgba(0, 0, 0, 0.5);
cursor: move;
}

.container:hover .box {
transform: rotateY(180deg);
.resizable-box {
border: 2px dashed #000;
resize: both;
overflow: auto;
}
</style>

Loading…
Cancel
Save