想把div拖来拖去,又想对它进行大小控制,如果再美化点,做成像PS那样的,图形处理方式的,还有右击显示菜单的,就更好了,有没有呢,有,它就是 vue-draggable-resizable
感谢vue-draggable-resizable团队,解决我等码农的需求
这是我做出来的效果
下面开始步骤了
1.安装或者组件化
我是选择了私有组件化,因为我对它进行了二次开发,下面是直接新建文件夹放进去的的插件,(好处是与版本管理脱节,完全放心的DIY,化为了私有的)
2.有了插件,现在开始写字啦
建一个封装后的组件
内容
<!--节目编辑/图片拖拽组件-->
<template>
<vue-draggable-resizable
:w="item.width"
:h="item.height"
:x="item.x"
:y="item.y"
:z-index="id"
:is-conflict-check="false"
:snap="true"
:snap-tolerance="10"
:grid="[1,1]"
:parent-scale="parentScale"
:resizable="isresizable"
:draggable="isdraggable"
:parent="false"
class="img-drag-item"
@dragging="onDrag"
@dragstop="onDragstop"
@resizing="onResize"
@resizestop="onResizestop"
@activated="onActivated"
@deactivated="onDeActivated"
>
<div
v-if="item.type ===1"
:style="styleMediaObj"
class="middle-title"
@click="changeliveisResize(true)"
>
<!-- 图文 -->
<div
v-if="!bgcImgUrl"
:style="{backgroundColor: hexToRGBA(item.bgColor,item.bgOpacity),height:item.height - item.fontSize - 10 + 'px'}"
class="text-son">{{ $t('programModel.imageText') }}</div>
<div v-else>
<div :style="{backgroundSize: '100% 100%',backgroundImage: 'url(' + bgcImgUrl + ')',height:item.height - item.fontSize - 10 + 'px'}" class="text-son"></div>
</div>
<div :style="{backgroundColor: hexToRGBA(item.bgColor,item.bgOpacity), padding:'3px',fontSize: item.fontSize + 'px', color: item.fontColor,height:item.fontSize+10+'px'}" class="left-align-text caption-text">
<font :face="item.font"> {{ item.text }} </font>
</div>
</div>
<!-- 图片 -->
<div
v-else-if="item.type ===2"
:style="styleMediaObj2"
class="middle-title"
@click="changeliveisResize(true)"
>
<div v-show="!bgcImgUrl" class="text-son">{{ $t('program.image') }}</div>
</div>
<!-- 文本 -->
<div
v-else-if="item.type ===3"
:style="styleObj"
style="border-radius: 5px"
class="left-title bgc-opacity"
@click="changeliveisResize(true)">
<div v-if="!item.text">
<div class="text-son">
<font :face="item.font">
{{ $t('program.text') }}
</font>
</div>
</div>
<div v-else>
<div :style="{fontSize: item.fontSize + 'px', color: item.fontColor}" class="left-align-text">
<font :face="item.font">{{ item.text }}</font>
</div>
</div>
</div>
<!-- 字幕 -->
<div
v-else-if="item.type ===4"
:style="styleObj"
style="border-radius: 5px"
class="left-title bgc-opacity"
@click="changeliveisResize(true)">
<div v-if="!item.text">
<div class="text-son">
<font :face="item.font">
{{ $t('program.caption') }}
</font>
</div>
</div>
<div v-else>
<div :style="{fontSize: item.fontSize + 'px', color: item.fontColor}" class="left-align-text caption-text">
<font :face="item.font">
{{ item.text }}
</font>
</div>
</div>
</div>
<!-- 容器 -->
<div
v-else-if="item.type ===5"
:style="{backgroundColor: hexToRGBA(item.bgColor,item.bgOpacity)}"
class="middle-title"
@click="changeliveisResize(true)"
>
<div class="text-son">{{ $t('programModel.classContainer') }}</div>
</div>
<!-- 滚动图-->
<div
v-else-if="item.type ===6"
:style="styleMediaObj2"
class="middle-title"
@click="changeliveisResize(true)"
>
<!-- 多媒体 -->
<div v-show="!bgcImgUrl" class="text-son">{{ $t('programModel.banner1') }}</div>
</div>
</vue-draggable-resizable>
</template>
<script>
import VueDraggableResizable from '@/components/draggable-resizable/components/vue-draggable-resizable'
import '@/components/draggable-resizable/components/vue-draggable-resizable.css'
// import VueDraggableResizable from 'vue-draggable-resizable-gorkys'
// import 'vue-draggable-resizable-gorkys/dist/VueDraggableResizable.css'
import { parseTime2 } from '@/prototypeEx'
export default {
name: 'DragableItem',
components: {
VueDraggableResizable
},
filters: {
// parseTime(v) {
// return parseTime(v)
// }
//
},
props: {
id: {
type: Number,
required: true
},
item: {
type: Object,
default: () => ({})
},
scale: {
type: Number,
default: 1
},
isresizable: {
type: Boolean,
required: true,
default: true
},
heightisreadonly: {
type: Boolean,
required: true,
default: false
},
widthisreadonly: {
type: Boolean,
required: true,
default: false
},
isdraggable: {
type: Boolean,
required: true,
default: true
}
},
data() {
return {
imgItem: {
width: this.item.width,
height: this.item.height,
x: this.item.x,
y: this.item.y,
id: this.item.id
},
show: true,
parentScale: this.scale,
bgcImgUrl: '' // 模块背景图片地址
}
},
computed: {
styleObj() {
// 需要将十六进制颜色和透明度转换为rgba格式,背景透明度才不会影响内部的文本元素
return {
backgroundColor: this.hexToRGBA(this.item.bgColor, this.item.bgOpacity)
}
},
styleMediaObj() {
if (this.bgcImgUrl) {
return {
backgroundSize: '100% 100%'
}
}
},
styleMediaObj2() {
if (this.bgcImgUrl) {
return {
backgroundImage: 'url(' + this.bgcImgUrl + ')',
backgroundSize: '100% 100%'
}
} else {
return {
backgroundColor: this.hexToRGBA(this.item.bgColor, this.item.bgOpacity)
}
}
},
today() {
const time = new Date()
return parseTime2(time, 'yyyy-MM-dd HH:mm:ss EEEE')
}
},
watch: {
// 深度监听模块有没有选中素材元素,有就需要更新模块背景图片地址
item: {
immediate: true, // fix 第一次获取数据不加载背景图片的bug
handler: function(val) {
if (this.item.pic || (this.item.picUnitList && this.item.picUnitList.length > 0)) {
this.baseCallback(this.item.pic ? this.item.pic : this.item.picUnitList[0].pic)
} else {
this.baseCallback()
}
this.item.text = val.text
this.imgItem.x = val.x
this.imgItem.y = val.y
this.imgItem.width = val.width
this.imgItem.height = val.height
console.log(this.imgItem.y)
},
deep: true
},
scale(val, oldVal) {
if (val === oldVal) {
return
}
this.parentScale = val
}
},
mounted() {
},
created() {
var that = this
document.onkeydown = function(e) {
const e1 = e || event || window.event
// 键盘按键判断:左箭头-37;上箭头-38;右箭头-39;下箭头-40
// 左
if (e1 && e1.keyCode == 37) {
that.$emit('changingData_keypress', {
x: -1,
y: 0
})
} else if (e1 && e1.keyCode == 38) {
that.$emit('changingData_keypress', {
x: 0,
y: -1
})
} else if (e1 && e1.keyCode == 39) {
that.$emit('changingData_keypress', {
x: 1,
y: 0
})
} else if (e1 && e1.keyCode == 40) {
that.$emit('changingData_keypress', {
x: 0,
y: 1
})
}
}
},
methods: {
today_format(fmt) {
const time = new Date()
return parseTime2(time, fmt)
},
activeData() {
// 向父组件实时更新前块的数据信息
this.$emit('changingData', {
x: this.imgItem.x,
y: this.imgItem.y,
width: this.imgItem.width,
height: this.imgItem.height,
type: this.item.type,
clickType: this.item.clickType > 0 ? this.item.clickType : undefined,
clickTarget: this.item.clickTarget ? this.item.clickTarget : undefined,
pageRolling: this.item.pageRolling >= 0 ? this.item.pageRolling : 0,
id: this.id
})
},
changeliveisResize(flag) {
this.$emit('changeisresizable', flag)
},
// 拖拽结束,尺寸修改结束后向父组件传递确定的位置信息
updateData() {
if (this.item.type === 10) {
this.$emit('updateFinalData', {
x: this.imgItem.x,
y: this.imgItem.y,
width: this.imgItem.width,
height: this.imgItem.height,
id: this.id
})
} else {
this.$emit('updateFinalData', {
x: this.imgItem.x,
y: this.imgItem.y,
width: this.imgItem.width,
height: this.imgItem.height,
id: this.id
})
}
},
onResize: function(x, y, width, height) {
if (this.item.type === 10) {
this.imgItem.x = x
this.imgItem.y = y
this.imgItem.width = width
this.imgItem.height = height
} else {
this.imgItem.x = x
this.imgItem.y = y
this.imgItem.width = width
this.imgItem.height = height
}
this.activeData()
this.$emit('noclonedate')
},
// 改变尺寸大小结束后
onResizestop() {
this.updateData()
},
onDrag: function(x, y) {
this.imgItem.x = x
this.imgItem.y = y
this.activeData()
this.$emit('noclonedate')
},
// 拖拽结束后
onDragstop() {
this.updateData()
},
deleteStudent: function() {
if (this.index !== 0) {
this.show = false
}
},
onActivated() {
this.activeData()
},
onDeActivated() {
this.activeData()
},
// 将网络请求的图片转换成base64字符
getBase64(url) {
return new Promise((resolve, reject) => {
var Img = new Image()
var dataURL = ''
Img.setAttribute('crossOrigin', 'Anonymous') // 跨域图片能正常裁剪
Img.src = url + '?v=' + Math.random()
Img.onload = function() { // 对象初始化加载完成后
// 要先确保图片完整获取到,这是个异步事件
var canvas = document.createElement('canvas') // 创建canvas元素
var width = Img.width // 确保canvas的尺寸和图片一样
var height = Img.height
canvas.width = width
canvas.height = height
canvas.getContext('2d').drawImage(Img, 0, 0, width, height) // 将图片绘制到canvas中
dataURL = canvas.toDataURL('image/png') // 转换图片为dataURL
resolve(dataURL)
}
})
},
// 获取base64图片数据赋值给属性
async baseCallback(url) {
try {
if (!url) {
// 多媒体素材一个都没有了,背景色应还原
this.bgcImgUrl = ''
return
}
const res = await this.getBase64(url) // 这种方案是将网络图片转换为base64格式再做背景显示处理,缺陷是对png格式处理不了
this.bgcImgUrl = res
} catch (err) {
console.log(err)
}
},
/**
* 十六进制颜色转换为带透明度的颜色
* @param _color 十六进制颜色
* @param _opactity 透明度
* @returns {string} rgba
*/
hexToRGBA(_color, _opacity) {
let sColor = _color.toLowerCase()
// 十六进制颜色值的正则表达式
const reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/
// 如果是16进制颜色
if (sColor && reg.test(sColor)) {
if (sColor.length === 4) {
let sColorNew = '#'
for (let i = 1; i < 4; i += 1) {
sColorNew += sColor.slice(i, i + 1).concat(sColor.slice(i, i + 1))
}
sColor = sColorNew
}
// 处理六位的颜色值
const sColorChange = []
for (let i = 1; i < 7; i += 2) {
sColorChange.push(parseInt('0x' + sColor.slice(i, i + 2)))
}
return 'rgba(' + sColorChange.join(',') + ',' + _opacity + ')'
}
return sColor
}
}
}
</script>
<style lang="scss" scoped>
.img-drag-item {
// border: 1px solid #67c23a;
border: 1px solid rgba(0,0,0,0);
// background-color: #f7fdf9;
// display: table; // fix firefox背景图不显示的bug
position: relative;
box-sizing: border-box;
cursor: move;
.middle-title {
vertical-align: middle;
text-align: center;
height: 100%;
width: 100%;
font-size: 30px;
border-radius: 5px;
// background-color: #f7fdf9;
overflow: hidden;
.text-son {
height: 100%;
font-size: 1rem;
position: relative;
}
.text-son::before {
display: inline-block;
content: '';
height: 100%;
vertical-align: middle;
}
}
.bgc-color {
background-color: #f7fdf9;
}
.left-title {
height: 100%;
width: 100%;
font-size: 30px;
background-color: #f7fdf9;
overflow: hidden;
.text-son {
font-size: 1rem;
}
.text-son::before {
display: inline-block;
content: '';
height: 100%;
vertical-align: middle;
}
}
.left-align-text {
text-align: left;
word-wrap:break-word;
word-break:break-all;
}
.caption-text {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.bgc-opacity {
background-color:transparent;
// opacity: 0.1;
}
}
// 块激活样式
.active {
border: 1px solid #67c23a;
}
</style>
版权声明:本文为u012174809原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。