Commit ec13bfcc authored by 毛线's avatar 毛线

组件测试

parents
/* Automatically generated by './build/bin/build-entry.js' */
import CyCanvas from './packages/canvas/index.js'
const components = [
CyCanvas,
]
const install = function(Vue, opts = {}) {
components.forEach(component => {
Vue.component(component.name, component)
})
}
/* istanbul ignore if */
if (typeof window !== 'undefined' && window.Vue) {
install(window.Vue)
}
export default {
version: '1.0.0',
install,
}
{
"name": "ruitu-magic-canvas",
"version": "1.0.0",
"description": "瑞图魔术手画布组件",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git@cy-dev.com:maoxian/ruitu-magic-canvas.git"
},
"keywords": [
"瑞图魔术手画布组件"
],
"author": "毛线",
"license": "ISC"
}
import Canvas from './src/index'
/* istanbul ignore next */
Canvas.install = function(Vue) {
Vue.component(Canvas.name, Canvas)
}
export default Canvas
<template>
<div class="layout">
<div :style="styles">
<canvas ref="canvas"/>
</div>
<!-- <div class="testbox">test</div> -->
<!-- <canvas ref="canvas2" style="background: #ccc;"/> -->
</div>
</template>
<script>
export default {
props: {
value: {
type: Object,
default: () => {
return {}
},
}
},
data() {
return {
canvas: '',
data: {
// 画布参数
w: 1024, // 单位px
h: 1920, // 单位px
bc: '',
bg: '',
// 卡片信息
child: [],
},
}
},
computed: {
styles() {
const { data } = this
const { bg, w, h, bc, } = data
const styles = {}
styles.backgroundColor = bc || 'transparent'
// bg && (styles.backgroundImage = `url(${bg})`)
styles.width = `${w}px` || '500px'
styles.height = `${h}px` || '500px'
return styles
},
testStyle() {
const { testPoint } = this
const { x, y } = testPoint
const styles = {}
styles.top = `${y}px` || '0px'
styles.left = `${x}px` || '500px'
return styles
},
},
watch: {
value(value) {
console.log('watch canvas value', value)
const data = this.initData(value)
if (JSON.stringify(data) !== JSON.stringify(this.data)) {
this.data = data
}
},
data() {
console.log('watch data', this.data)
const { activeIndex, data } = this
this.$emit('on-change', { data, index: activeIndex }) // 选中的索引切换的时候,触发回调
this.draw()
},
activeIndex(index) {
const { data } = this
this.$emit('on-change', { data, index }) // 选中的索引切换的时候,触发回调
},
},
created() {
console.log('created', this.value)
this.data = this.initData(this.value)
},
mounted() {
this.draw()
// this.init2()
},
methods: {
draw() {
const { data } = this
const { w, h } = data
const canvas = this.$refs.canvas
const { child } = data
canvas.width = w
canvas.height = h
const ctx = canvas.getContext('2d')
const handle = (property) => {
return function(a, b) {
const val1 = a[property]
const val2 = b[property]
return val1 - val2
}
}
if (child) {
const drawList = [...child]
drawList.sort(handle('z'))
drawList.forEach(item => {
const { x, y, w, h, bg, deg } = item
const card = new Card({
x,
y,
width: w,
height: h,
bgImg: bg,
deg,
})
card.draw(ctx)
})
}
// const card = new Card({
// x: 10,
// y: 10,
// width: 150,
// height: 150,
// bgImg: 'https://shengdi-image.oss-cn-shenzhen.aliyuncs.com/2023/05/30/tmp_b457b0ef3788dec5abcc5626ed03fe47',
// })
// card.draw(ctx)
// const card2 = new Card({
// x: 400,
// y: 100,
// width: 400,
// height: 400,
// bgImg: 'https://img2.baidu.com/it/u=2207107653,2475297256&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=281',
// })
// card2.draw(ctx)
},
// 初始化数据
initData(value) {
const data = {
w: 1024, // 单位px
h: 1920, // 单位px
child: [],
...value
}
return data
},
},
}
function Card(params) {
const { x, y, width, height, bgImg, deg } = params
console.log('params', params)
this.x = x // x 坐标
this.y = y // y 坐标
this.width = width // 宽度
this.height = height // 高度
this.bgImg = bgImg // 图片路径
// this.draw = function(ctx) {
// ctx.fillStyle = 'red'
// // 计算矩形的中心
// const centerX = x + width / 2
// const centerY = y + height / 2
// // ctx.translate(-centerX, -centerY)
// ctx.fillRect(this.x, this.y, this.width, this.height)
// // ctx.translate(centerX, centerY)
// }
this.draw = function(ctx) {
const img = new Image()
img.src = this.bgImg
img.onload = () => {
// 计算图片缩放比例
const imgWidth = img.width
const imgHeight = img.height
const scaleX = width / imgWidth
const scaleY = height / imgHeight
// 创建图案
var pattern = ctx.createPattern(img, 'no-repeat')
// ctx.scale(scaleX, scaleY)
ctx.fillStyle = pattern
// 计算矩形的中心
const centerX = x + width / 2
const centerY = y + height / 2
// console.log('centerX', centerX)
// console.log('centerY', centerY)
// ctx.save() // 保存当前状态
// ctx.rotate(Math.PI * (deg / 180))
// ctx.rotate(-Math.PI * (deg / 180))
ctx.translate(centerX, centerY)
ctx.rotate(Math.PI * (deg / 180))
ctx.translate(-centerX, -centerY)
ctx.drawImage(img, 0, 0, img.width, img.height, x, y, width, height)
ctx.translate(centerX, centerY)
ctx.rotate(Math.PI * (-deg / 180))
ctx.translate(-centerX, -centerY)
// ctx.translate(-centerX, -centerY)
// ctx.rotate(Math.PI / 4)
// // ctx.translate(centerX, centerY)
// ctx.drawImage(img, 0, 0, img.width, img.height, x, y, width, height)
// ctx.translate(centerX, centerY)
// ctx.restore()
console.log('渲染图片')
}
}
}
// 绘制矩形
function drawRect() {
}
</script>
<style lang="scss" scoped>
.layout{
position: relative;
}
.testbox {
position: absolute;
top: 100px;
left: 400px;
background: red;
height: 100px;
width: 100px;
}
.test {
position: absolute;
left: 0;
top: 0;
}
</style>
<template>
<!-- 操作按钮 -->
<div
:class="{show: showOptions,}"
class="options"
@click.stop=""
@mousedown="(event) => move(event)"
>
<!-- 左上角点 -->
<div
style="cursor: nwse-resize;"
class="top-left move-point"
@mousedown.stop="(event) => movePoint(event, 'leftTop')"
/>
<!-- 右上角点 -->
<div
style="cursor: nesw-resize;"
class="top-right move-point"
@mousedown.stop="(event) => movePoint(event, 'rightTop')"
/>
<!-- 左下角点 -->
<div
style="cursor: nesw-resize;"
class="bottom-left move-point"
@mousedown.stop="(event) => movePoint(event, 'leftBottom')"
/>
<!-- 右下角点 -->
<div
style="cursor: nwse-resize;"
class="bottom-right move-point"
@mousedown.stop="(event) => movePoint(event, 'rightBottom')"
/>
<!-- 顶部中间点 -->
<div
style="cursor: ns-resize;"
class="top move-point"
@mousedown.stop="(event) => movePoint(event, 'centerTop')"
/>
<!-- 底部中间点 -->
<div
style="cursor: ns-resize;"
class="bottom move-point"
@mousedown.stop="(event) => movePoint(event, 'centerBottom')"
/>
<!-- 左边中间点 -->
<div
style="cursor: ew-resize;"
class="left move-point"
@mousedown.stop="(event) => movePoint(event, 'leftCenter')"
/>
<!-- 右边中间点 -->
<div
style="cursor: ew-resize;"
class="right move-point"
@mousedown.stop="(event) => movePoint(event, 'rightCenter')"
/>
<!-- 旋转点 -->
<div
class="rotate"
@click.stop=""
@mousedown.stop="rotate">
<img class="icon" src="./assets/rotate.png" alt="">
</div>
</div>
</template>
<script>
function getYAxisAngle(point1, point2) {
var xDiff = point2.x - point1.x
var yDiff = point2.y - point1.y
var angleRadians = Math.atan2(yDiff, xDiff)
var angleDegrees = angleRadians * 180 / Math.PI
return (angleDegrees + 90)
}
export default {
props: {
value: {
type: Object,
default: () => {
return {}
},
},
// 显示操作按钮
showOptions: {
type: Boolean,
default: false,
},
// 画布宽度
canvasWidth: {
type: [String, Number],
default: '',
},
// 画布高度
canvasHeight: {
type: [String, Number],
default: '',
},
},
data() {
return {
data: {},
minWitdth: 10, // box 最小宽度
minHeight: 10, // box 最小高度
jieliu: false,
}
},
watch: {
value(value) {
const data = value
if (JSON.stringify(data) !== JSON.stringify(this.data)) {
this.data = data
}
},
data(data) {
this.$emit('input', data)
},
},
created() {
this.data = this.value
},
methods: {
move(event) {
const { data } = this
const { x, y, } = data
const dataX = x
const dataY = y
const odiv = event.target // 获取目标元素
const diff = -4 // diff是card组件,操作区域距离图层的距离
// this.$emit('test', { x: roundDotX, y: roundDotY }) // 此方法用来显示指定点击的点
// 算出鼠标相对元素的位置
const disX = event.clientX - odiv.offsetLeft + diff
const disY = event.clientY - odiv.offsetTop + diff
document.onmousemove = (e) => { // 鼠标按下并移动的事件
// 用鼠标的位置减去鼠标相对元素的位置,得到元素的位置
const left = dataX + e.clientX - disX
const top = dataY + e.clientY - disY
data.x = left
data.y = top
}
document.onmouseup = (e) => {
document.onmousemove = null
document.onmouseup = null
console.log('---onmouseup---移动结束')
this.$emit('on-change')
}
},
// 旋转判断
rotate(event) {
const { data, } = this
const { x, y, w, h, deg, } = data
const roundDotX = x + w / 2
const roundDotY = y + h / 2
const rotateDomMargin = 40 // 点击按钮距离底部高度
const iconX = roundDotX - Math.sin(deg / 180 * Math.PI) * (h / 2 + rotateDomMargin) // 计算出按钮的实际坐标
const iconY = roundDotY + Math.cos(deg / 180 * Math.PI) * (h / 2 + rotateDomMargin) // 计算出按钮的实际坐标
let startX = ''
let startY = ''
document.onmousemove = (e) => {
if (startX === '') startX = e.clientX
if (startY === '') startY = e.clientY
const point1 = { x: (e.clientX + iconX - startX), y: (e.clientY + iconY - startY) }
const point2 = { x: roundDotX, y: roundDotY }
const deg = getYAxisAngle(point1, point2)
this.data.deg = deg // 设置角度
}
document.onmouseup = (e) => {
document.onmousemove = null
document.onmouseup = null
console.log('---onmouseup---移动结束')
this.$emit('on-change')
}
},
movePoint(event, type) {
let startX = ''
let startY = ''
const { data, minWitdth, minHeight, canvasWidth, canvasHeight, } = this
const { x, y, w, h, } = data
const dataX = x
const dataY = y
const dataW = w
const dataH = h
const rotate = 30 // 旋转角度
document.onmousemove = (e) => {
if (this.jieliu) {
return
}
this.jieliu = true
setTimeout(() => {
this.jieliu = false
}, 1)
if (startX === '') startX = e.clientX
if (startY === '') startY = e.clientY
const moveX = e.clientX * 1 - startX * 1
const moveY = e.clientY * 1 - startY * 1
switch (type) {
case 'leftTop':
if (!(this.data.w < minWitdth && dataW - moveX < this.data.w)) {
this.data.x = dataX * 1 + moveX * 1
this.data.w = dataW * 1 - moveX * 1
}
if (!(this.data.h < minHeight && dataH - moveY < this.data.h)) {
this.data.y = dataY * 1 + moveY * 1
this.data.h = dataH * 1 - moveY * 1
}
break
case 'centerTop':
if (!(this.data.h < minHeight && dataH - moveY < this.data.h)) {
this.data.h = dataH * 1 - moveY * 1
this.data.y = dataY * 1 + moveY * 1
}
break
case 'rightTop':
if (!(this.data.w < minWitdth && dataW + moveX < this.data.w)) {
this.data.w = dataW * 1 + moveX * 1
}
if (!(this.data.h < minHeight && dataH - moveY < this.data.h)) {
this.data.y = dataY * 1 + moveY * 1
this.data.h = dataH * 1 - moveY * 1
}
break
case 'leftCenter':
if (!(this.data.w < minWitdth && dataW - moveX < this.data.w)) {
this.data.x = dataX * 1 + moveX * 1
this.data.w = dataW * 1 - moveX * 1
}
break
case 'rightCenter':
if (!(this.data.w < minWitdth && dataW + moveX < this.data.w)) {
const scale = Math.cos(rotate * Math.PI / 180)
this.data.w = dataW * 1 + moveX * 1
}
break
case 'leftBottom':
if (!(this.data.w < minWitdth && dataW - moveX < this.data.w)) {
this.data.x = dataX * 1 + moveX * 1
this.data.w = dataW * 1 - moveX * 1
}
if (!(this.data.h < minHeight && dataH + moveY < this.data.h)) {
this.data.h = dataH * 1 + moveY * 1
}
break
case 'centerBottom':
console.log('centerBottom')
if (!(this.data.h < minHeight && dataH + moveY < this.data.h)) {
this.data.h = dataH * 1 + moveY * 1
}
break
case 'rightBottom':
if (!(this.data.w < minWitdth && dataW + moveX < this.data.w)) {
this.data.w = dataW * 1 + moveX * 1
}
if (!(this.data.h < minHeight && dataH + moveY < this.data.h)) {
this.data.h = dataH * 1 + moveY * 1
}
break
default:
break
}
}
document.onmouseup = (e) => {
document.onmousemove = null
document.onmouseup = null
// document.onmousedown = null;
console.log('---onmouseup---拖拽结束')
this.$emit('on-change')
}
},
},
}
</script>
<style lang="scss" scoped>
// 操作按钮区域
$diff: -4px;
.options {
position: absolute;
top: $diff;
left: $diff;
right: $diff;
bottom: $diff;
display: none;
&.show {
display: block;
}
.move-point {
width: 8px;
height: 8px;
border-radius: 100%;
z-index: 9999;
background-color: #09f;
position: absolute;
user-select: none;
$model: calc(50% - 4px);
&.top-left {
left: 0;
top: 0;
}
&.top-right {
right: 0;
top: 0;
}
&.bottom-left {
bottom: 0;
left: 0;
}
&.bottom-right {
bottom: 0;
right: 0;
}
&.top {
top: 0;
left: $model;;
}
&.bottom {
bottom: 0;
left: $model;
}
&.left {
left: 0;
top: $model;
}
&.right {
right: 0;
top: $model;;
}
}
.rotate {
user-select: none;
-webkit-user-drag: none;
bottom: -40px;
left: calc(50% - 10px);
position: absolute;
.icon {
user-select: none;
-webkit-user-drag: none;
width: 20px;
height: 20px;
}
}
}
</style>
<template>
<div
:style="styles"
:class="{
'active': showOptions,
}"
class="card"
@click.stop="$emit('click', {id, data,})"
>
<!-- 操作按钮 -->
<CardOptions
v-model="data"
:show-options="showOptions"
:canvas-width="canvasWidth"
:canvas-height="canvasHeight"
@on-change="$emit('on-change')"
@test="(point) => $emit('test', point)"
/>
<div style="width: 100%; height: 100%;">
<!-- <span v-if="!data.bg">{{ data.n }}</span>
<div v-if="data.bg" :style="`background-image:url(${data.bg})`" alt="" class="bg"/> -->
</div>
</div>
</template>
<script>
import CardOptions from './card-options'
export default {
components: {
CardOptions,
},
props: {
value: {
type: Object,
default: () => {
return {}
},
},
id: {
type: [Number, String],
default: 0,
},
// 显示操作按钮
showOptions: {
type: Boolean,
default: false,
},
// 画布宽度
canvasWidth: {
type: [String, Number],
default: '',
},
// 画布高度
canvasHeight: {
type: [String, Number],
default: '',
},
},
data() {
return {
data: {},
}
},
computed: {
styles() {
const { data, showOptions } = this
const { bg, w, h, x, y, z, deg, o, } = data
const styles = {}
styles.backgroundColor = bg || '#00000011'
styles.width = `${w}px`
styles.height = `${h}px`
styles.top = `${y}px`
styles.left = `${x}px`
styles.zIndex = showOptions ? '999999' : z || 0
styles.transform = `rotateZ(${deg}deg)`
styles.opacity = o
return styles
},
},
watch: {
value(value) {
const data = this.initData(value)
if (JSON.stringify(data) !== JSON.stringify(this.data)) {
this.data = data
}
},
data(data) {
this.$emit('input', data)
},
},
created() {
this.data = this.initData(this.value)
},
methods: {
initData(value) {
const data = {
n: '', // 单位px
w: 100, // 单位px
h: 100, // 单位px
x: 0, // 坐标
y: 0, // 坐标
z: 1, // 层级
o: 1, // 透明度
bg: '', // 背景图
deg: 0, // 旋转角度
mbg: '', // 遮罩图片
tbg: '', // 顶部图片
...value
}
return data
},
},
}
</script>
<style lang="scss" scoped>
.card {
position: absolute;
cursor: pointer;
/* transform: rotateZ(30deg); */
&.active {
opacity: 0.8; // 选中的时候,半透明效果
}
/* transform: rotateZ(30deg); */
.bg {
width: 100%;
height: 100%;
background-size: 100% 100%;
background-repeat: no-repeat;
}
// 操作按钮区域
$diff: -4px;
.options {
position: absolute;
top: $diff;
left: $diff;
right: $diff;
bottom: $diff;
display: none;
&.show {
display: block;
}
.move-point {
width: 8px;
height: 8px;
border-radius: 100%;
z-index: 9999;
background-color: #09f;
position: absolute;
$model: calc(50% - 4px);
&.top-left {
left: 0;
top: 0;
}
&.top-right {
right: 0;
top: 0;
}
&.bottom-left {
bottom: 0;
left: 0;
}
&.bottom-right {
bottom: 0;
right: 0;
}
&.top {
top: 0;
left: $model;;
}
&.bottom {
bottom: 0;
left: $model;
}
&.left {
left: 0;
top: $model;
}
&.right {
right: 0;
top: $model;;
}
}
}
}
</style>
<template>
<div class="layout">
<div
:style="styles"
class="div-content"
@click="handler().clickCanvas()">
<template v-for="(item, index) in data.child" >
<Card
v-model="data.child[index]"
:key="index"
:id="index"
:show-options="activeIndex === index"
:canvas-width="data.w"
:canvas-height="data.h"
@test="(point) => handler().test(point)"
@on-change="() => handler().onChangeCard()"
@click="(data) => handler().clickCard(data)"
/>
</template>
<!-- 测试div -->
<div :style="testStyle" class="testPoint"/>
</div>
<Canvas ref="canvas" :value="data" class="canvas" />
</div>
</template>
<script>
import Canvas from './canvas'
import Card from './card'
export default {
name: 'CyCanvas',
components: {
Canvas,
Card,
},
props: {
value: {
type: Object,
default: () => {
return {}
},
}
},
data() {
return {
data: {
// 画布参数
w: 1024, // 单位px
h: 1920, // 单位px
// 卡片信息
child: [],
},
activeIndex: '', // 选中的图层索引
testPoint: {
x: 0,
y: 0,
},
}
},
computed: {
styles() {
const { data } = this
const { bg, w, h, bc, } = data
const styles = {}
// styles.backgroundColor = bc || 'transparent'
// bg && (styles.backgroundImage = `url(${bg})`)
styles.width = `${w}px` || '500px'
styles.height = `${h}px` || '500px'
return styles
},
testStyle() {
const { testPoint } = this
const { x, y } = testPoint
const styles = {}
styles.top = `${y}px` || '0px'
styles.left = `${x}px` || '500px'
return styles
},
},
watch: {
value(value) {
const data = this.initData(value)
console.log('watch value', value)
if (JSON.stringify(data) !== JSON.stringify(this.data)) {
this.data = data
}
},
data() {
const { activeIndex, data } = this
this.$emit('on-change', { data, index: activeIndex }) // 选中的索引切换的时候,触发回调
console.log('watch data', data)
},
activeIndex(index) {
const { data } = this
this.$emit('on-change', { data, index }) // 选中的索引切换的时候,触发回调
},
},
created() {
this.data = this.initData(this.value)
},
methods: {
// 重新渲染
init() {},
// 初始化数据
initData(value) {
const data = {
w: 1024, // 单位px
h: 1920, // 单位px
child: [],
...value
}
return data
},
setCanvasData(data) {
const { bg } = data
bg && (this.data.bg = bg)
},
// 设置卡片的数据
// data的数据结构
// data = {
// t: '', // type,(i:图片, p:手机, sp:侧面手机, va:可视区域)
// w: 100, // 单位px
// h: 100, // 单位px
// x: 0, // 坐标
// y: 0, // 坐标
// z: 1, // 层级
// o: 1, // 透明度
// bg: '', // 背景图
// deg: 0, // 旋转角度
// mbg: '', // 遮罩图片
// tbg: '', // 顶部图片
// }
// index:传值则是编辑,传空字符串则是添加新的板块
setCardData(data, index) {
if (index === '') {
this.data.child.push(data) // 新增一个
} else {
this.data.child.splice(index, 1, { ...this.data.child[index], ...data }) // 需要改、替换
}
this.$emit('on-change', { data: this.data, index })
this.$nextTick(() => {
this.$refs.canvas.draw()
})
},
deleteCard(index) {
this.data.child.splice(index, 1)
this.$emit('on-change', { data: this.data, index: '' })
this.$refs.canvas.draw()
},
// 导出图片
exportImg() {
// this.$Screenshot(document.querySelector('#wrapper')).then(canvas => {
// this.downFile(canvas.toDataURL('image/jpeg', 0.8), new Date().getTime());
// this.$message.success('图片导出成功')
// })
},
// 定位到某个图层
selectIndex(index) {
this.activeIndex = (Number)(index)
},
handler() {
return {
// 点击了卡片
clickCard: ({ id, data, }) => {
this.activeIndex = id // 选中图层
},
clickCanvas: () => {
this.activeIndex = '' // 取消图层选中
},
onChangeCard: () => {
const { activeIndex, data } = this
this.$emit('on-change', { data, index: activeIndex }) // 选中的索引切换的时候,触发回调
this.$refs.canvas.draw()
},
test: (point) => {
this.testPoint = point
},
}
},
},
}
</script>
<style lang="scss" scoped>
.layout {
position: relative;
}
.div-content {
position: relative;
background-size: 100%;
background-repeat: no-repeat;
z-index: 1;
/* overflow: hidden; */
}
.canvas {
position: absolute;
top: 0;
left: 0;
}
.testPoint {
display: none;
background: red;
width: 4px;
height: 4px;
position: absolute;
}
</style>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment