├── babel.config.js
├── public
├── favicon.ico
└── index.html
├── src
├── assets
│ └── logo.png
├── views
│ ├── About.vue
│ └── Home.vue
├── store
│ ├── index.js
│ └── modules
│ │ ├── template.js
│ │ └── material.js
├── main.js
├── router.js
├── App.vue
├── libs
│ ├── util.js
│ ├── config.js
│ └── list.js
└── components
│ ├── MaterialStage
│ ├── LayerFont.vue
│ ├── index.vue
│ └── LayerGroup.vue
│ ├── MaterialConfig.vue
│ ├── TemplateStage
│ ├── LayerFont.vue
│ ├── index.vue
│ └── LayerGroup.vue
│ ├── CommonSliderBar.vue
│ ├── Material.vue
│ ├── Template.vue
│ └── TemplateConfig.vue
├── .gitignore
├── README.md
└── package.json
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/app'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DrLambs/vue-konva-project/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DrLambs/vue-konva-project/HEAD/src/assets/logo.png
--------------------------------------------------------------------------------
/src/views/About.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
This is an about page
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/views/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Hello Vue.js
4 |
5 |
6 |
7 |
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 | # local env files
6 | .env.local
7 | .env.*.local
8 |
9 | # Log files
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 |
14 | # Editor directories and files
15 | .idea
16 | .vscode
17 | *.suo
18 | *.ntvs*
19 | *.njsproj
20 | *.sln
21 | *.sw*
22 |
23 | pagckage-lock.json
24 | yarn.lock
25 |
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuex from 'vuex'
3 | import template from './modules/template'
4 | import material from './modules/material'
5 |
6 | Vue.use(Vuex)
7 |
8 | export default new Vuex.Store({
9 | state: {
10 | //
11 | },
12 | mutations: {
13 | //
14 | },
15 | actions: {
16 | //
17 | },
18 | modules: {
19 | template,
20 | material
21 | }
22 | })
23 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import App from './App.vue'
3 | import router from './router'
4 | import store from './store'
5 | import iView from 'iview'
6 | import 'iview/dist/styles/iview.css'
7 | import VueKonva from 'vue-konva'
8 |
9 | Vue.use(iView)
10 | Vue.use(VueKonva)
11 |
12 | Vue.config.productionTip = false
13 |
14 | new Vue({
15 | router,
16 | store,
17 | render: h => h(App)
18 | }).$mount('#app')
19 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # vue-konva-project
2 |
3 | ## [Vue Konva - 基于 Canvas 开发的 2d JavaScript框架库 - 使用整理【持续更新……】](https://blog.csdn.net/sansan_7957/article/details/81952061)
4 |
5 | ## 注意:需要用一下命令打开谷歌以解决跨域问题
6 | ```
7 | open -a /Applications/Google\ Chrome.app --args --disable-web-security --user-data-dir
8 | ```
9 |
10 | ## Project setup
11 | ```
12 | yarn install
13 | ```
14 |
15 | ### Compiles and hot-reloads for development
16 | ```
17 | yarn run serve
18 | ```
19 |
20 | ### Compiles and minifies for production
21 | ```
22 | yarn run build
23 | ```
24 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | vue-konva-project
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/router.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Router from 'vue-router'
3 | import Template from './components/Template'
4 | import Material from './components/Material'
5 |
6 | Vue.use(Router)
7 |
8 | export default new Router({
9 | mode: 'history',
10 | base: process.env.BASE_URL,
11 | routes: [
12 | // {
13 | // path: '/',
14 | // name: 'home',
15 | // component: Home
16 | // },
17 | {
18 | path: '/',
19 | name: 'template',
20 | component: Template
21 | },
22 | {
23 | path: '/material',
24 | name: 'material',
25 | // route level code-splitting
26 | // this generates a separate chunk (about.[hash].js) for this route
27 | // which is lazy-loaded when the route is visited.
28 | component: Material
29 | }
30 | ]
31 | })
32 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
12 |
13 |
14 |
37 |
--------------------------------------------------------------------------------
/src/libs/util.js:
--------------------------------------------------------------------------------
1 | import { Message } from "iview";
2 | const util = {};
3 |
4 | /**
5 | * 错误提示
6 | * @param msg
7 | */
8 | util.showError = function (msg) {
9 | Message.destroy()
10 | Message.error({
11 | content: msg,
12 | duration: 30,
13 | closable: true
14 | })
15 | }
16 | /**
17 | * 成功提示
18 | * @param msg
19 | */
20 | util.showSuccess = function (msg) {
21 | Message.destroy()
22 | Message.success({
23 | content: msg,
24 | duration: 5,
25 | closable: true
26 | })
27 | }
28 | /**
29 | * 警告提示
30 | * @param msg
31 | */
32 | util.showWarning = function (msg) {
33 | Message.destroy()
34 | Message.warning({
35 | content: msg,
36 | duration: 10,
37 | closable: true
38 | })
39 | }
40 | /**
41 | * 生成图片
42 | * @param src 图片路径
43 | */
44 | util.newImage = function (src) {
45 | let img = new Image();
46 | img.src = src;
47 | return img;
48 | }
49 |
50 | export default util;
51 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-konva-project",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build",
8 | "lint": "vue-cli-service lint"
9 | },
10 | "dependencies": {
11 | "iview": "^3.3.3",
12 | "konva": "^3.2.2",
13 | "lodash": "^4.17.11",
14 | "vue": "^2.6.6",
15 | "vue-konva": "^2.0.3",
16 | "vue-router": "^3.0.1",
17 | "vuex": "^3.0.1"
18 | },
19 | "devDependencies": {
20 | "@vue/cli-plugin-babel": "^3.0.5",
21 | "@vue/cli-plugin-eslint": "^3.0.5",
22 | "@vue/cli-service": "^3.0.5",
23 | "babel-eslint": "^10.0.1",
24 | "eslint": "^5.8.0",
25 | "eslint-plugin-vue": "^5.0.0",
26 | "less": "^3.9.0",
27 | "less-loader": "^4.1.0",
28 | "vue-template-compiler": "^2.5.21"
29 | },
30 | "eslintConfig": {
31 | "root": true,
32 | "env": {
33 | "node": true
34 | },
35 | "extends": [
36 | "plugin:vue/essential",
37 | "eslint:recommended"
38 | ],
39 | "rules": {},
40 | "parserOptions": {
41 | "parser": "babel-eslint"
42 | }
43 | },
44 | "postcss": {
45 | "plugins": {
46 | "autoprefixer": {}
47 | }
48 | },
49 | "browserslist": [
50 | "> 1%",
51 | "last 2 versions",
52 | "not ie <= 8"
53 | ]
54 | }
55 |
--------------------------------------------------------------------------------
/src/components/MaterialStage/LayerFont.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
22 |
23 |
33 |
34 |
35 |
44 |
45 |
46 |
47 |
48 |
49 |
62 |
--------------------------------------------------------------------------------
/src/libs/config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | config: {
3 | group: {
4 | uniqueId: 0,
5 | type: 'rect',
6 | x: 10,
7 | y: 10,
8 | width: 188,
9 | height: 244,
10 | draggable: true,
11 | rotation: 0,
12 | scale: 1,
13 | disabled: false,
14 | side: 300,
15 | radius: 150,
16 | stroke: 'transparent',
17 | strokeWidth: 1,
18 | src: 'http://uploads.5068.com/allimg/1712/151-1G204111207.jpg'
19 | },
20 | font: {
21 | uniqueId: 0,
22 | type: 'font',
23 | x: 200,
24 | y: 200,
25 | width: 246,
26 | height: 149,
27 | draggable: true,
28 | rotation: 0,
29 | scale: 1,
30 | fontText: '在这里输入文字',
31 | fontFamily: 'Sans-serif',
32 | fontSize: 24,
33 | fontColor: '#70FFF5',
34 | disabled: false,
35 | stroke: 'transparent',
36 | strokeWidth: 1,
37 | src: 'http://uploads.5068.com/allimg/1712/151-1G2050U519.jpg'
38 | }
39 | },
40 | initConfig: {
41 | group: {
42 | uniqueId: 0,
43 | type: 'rect',
44 | x: 10,
45 | y: 1,
46 | width: 188,
47 | height: 244,
48 | draggable: true,
49 | rotation: 0,
50 | scale: 1,
51 | disabled: false,
52 | side: 300,
53 | radius: 150,
54 | stroke: 'transparent',
55 | strokeWidth: 1,
56 | src: 'http://uploads.5068.com/allimg/1712/151-1G204111207.jpg'
57 | },
58 | font: {
59 | uniqueId: 0,
60 | type: 'font',
61 | x: 200,
62 | y: 200,
63 | width: 246,
64 | height: 149,
65 | draggable: true,
66 | rotation: 0,
67 | scale: 1,
68 | fontText: '在这里输入文字',
69 | fontFamily: 'Sans-serif',
70 | fontSize: 24,
71 | fontColor: '#70FFF5',
72 | disabled: false,
73 | stroke: 'transparent',
74 | strokeWidth: 1,
75 | src: 'http://uploads.5068.com/allimg/1712/151-1G2050U519.jpg'
76 | }
77 | }
78 | }
--------------------------------------------------------------------------------
/src/components/MaterialConfig.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
34 |
35 |
36 |
37 |
63 |
64 |
75 |
--------------------------------------------------------------------------------
/src/libs/list.js:
--------------------------------------------------------------------------------
1 | export default {
2 | // 形状列表
3 | shapeList: [
4 | {
5 | label: "矩形",
6 | value: "rect"
7 | }
8 | // {
9 | // label: '圆形',
10 | // value: 'circle'
11 | // },
12 | // {
13 | // label: '三角形',
14 | // value: 'triangle'
15 | // },
16 | // {
17 | // label: '心形',
18 | // value: 'heart'
19 | // }
20 | ],
21 | // 装饰类型
22 | decorationList: [
23 | {
24 | label: "文字",
25 | value: "font"
26 | },
27 | {
28 | label: "图片",
29 | value: "image"
30 | }
31 | ],
32 | // 字体列表
33 | fontFamilyList: [
34 | {
35 | id: 0,
36 | value: "Sans-serif"
37 | },
38 | {
39 | id: 1,
40 | value: "Helvetica"
41 | },
42 | {
43 | id: 2,
44 | value: "Arial"
45 | },
46 | {
47 | id: 3,
48 | value: "Verdana"
49 | },
50 | {
51 | id: 4,
52 | value: "Tahoma"
53 | },
54 | {
55 | id: 5,
56 | value: "Lucida Grande"
57 | },
58 | {
59 | id: 6,
60 | value: "Georgia"
61 | },
62 | {
63 | id: 7,
64 | value: "Arial Black"
65 | }
66 | ],
67 | // 字号列表
68 | fontSizeList: [
69 | {
70 | id: 12,
71 | value: "12px"
72 | },
73 | {
74 | id: 14,
75 | value: "14px"
76 | },
77 | {
78 | id: 16,
79 | value: "16px"
80 | },
81 | {
82 | id: 18,
83 | value: "18px"
84 | },
85 | {
86 | id: 20,
87 | value: "20px"
88 | },
89 | {
90 | id: 24,
91 | value: "24px"
92 | },
93 | {
94 | id: 32,
95 | value: "32px"
96 | },
97 | {
98 | id: 36,
99 | value: "36px"
100 | },
101 | {
102 | id: 44,
103 | value: "44px"
104 | },
105 | {
106 | id: 56,
107 | value: "56px"
108 | },
109 | {
110 | id: 60,
111 | value: "60px"
112 | },
113 | {
114 | id: 72,
115 | value: "72px"
116 | }
117 | ]
118 | }
--------------------------------------------------------------------------------
/src/components/MaterialStage/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
96 |
--------------------------------------------------------------------------------
/src/components/TemplateStage/LayerFont.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
26 |
27 |
39 |
40 |
41 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
100 |
--------------------------------------------------------------------------------
/src/store/modules/template.js:
--------------------------------------------------------------------------------
1 | import config from "../../libs/config";
2 |
3 | const template = {
4 | state: {
5 | stageConfig: {
6 | width: 960,
7 | height: 600,
8 | url: "http://img.pconline.com.cn/images/upload/upc/tx/wallpaper/1212/27/c0/16922564_1356570216970.jpg"
9 | },
10 | // config 类型: 模板框/装饰
11 | configType: 'group',
12 | // 表单初始化
13 | initConfig: config.initConfig,
14 | // 表单
15 | config: config.config,
16 | // 配置列表
17 | configList: {
18 | group: [],
19 | font: []
20 | },
21 | // 记录下一开始添加时的唯一ID
22 | uniqueId: {
23 | group: 0,
24 | font: 0
25 | },
26 | // 当前选中的ID
27 | currentId: {
28 | group: 0,
29 | font: 0
30 | },
31 | // 拖动定位
32 | dragStartX: 0,
33 | dragStartY: 0
34 | },
35 | mutations: {
36 | // 设置当前配置类型: group/font
37 | setConfigType(state, type) {
38 | state.configType = type;
39 | },
40 | // 添加配置
41 | add(state, config) {
42 | // id 增加
43 | state.config[state.configType].uniqueId++;
44 | state.uniqueId[state.configType] = state.config[state.configType].uniqueId;
45 | // 添加
46 | state.configList[state.configType].push(config);
47 | },
48 | // 保存配置
49 | save(state, config) {
50 | state.configList[state.configType].forEach((item, index) => {
51 | if (parseInt(item['uniqueId']) === state.currentId[state.configType]) {
52 | // 替换原来的
53 | state.configList[state.configType].splice(index, 1, config);
54 | }
55 | });
56 | // 初始化
57 | state.initConfig[state.configType].uniqueId = state.uniqueId[state.configType];
58 | state.config[state.configType] = state.initConfig[state.configType];
59 | },
60 | // 删除配置
61 | remove(state) {
62 | state.configList[state.configType].forEach((item, index) => {
63 | if (parseInt(item['uniqueId']) === state.currentId[state.configType]) {
64 | // 删除
65 | state.configList[state.configType].splice(index, 1);
66 | }
67 | });
68 | // 初始化
69 | state.initConfig[state.configType].uniqueId = state.uniqueId[state.configType];
70 | state.config[state.configType] = state.initConfig[state.configType];
71 | },
72 | // 获取当前配置
73 | current(state, config) {
74 | state.configList[state.configType].forEach(item => {
75 | item.stroke = 'transparent';
76 | });
77 | state.config[state.configType] = config;
78 | state.config[state.configType].stroke = '#0051FF';
79 | state.currentId[state.configType] = config.uniqueId;
80 | },
81 | start(state, {x, y}) {
82 | state.dragStartX = x - state.config[state.configType].x;
83 | state.dragStartY = y - state.config[state.configType].y;
84 | },
85 | move(state, {x, y}) {
86 | state.config[state.configType].x = x - state.dragStartX;
87 | state.config[state.configType].y = y - state.dragStartY;
88 | },
89 | end(state, {x, y}) {
90 | state.config[state.configType].x = x - state.dragStartX;
91 | state.config[state.configType].y = y - state.dragStartY;
92 | }
93 | }
94 | }
95 |
96 | export default template;
97 |
--------------------------------------------------------------------------------
/src/components/TemplateStage/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
13 |
14 |
15 |
16 |
23 |
24 |
31 |
32 |
33 |
34 |
35 |
104 |
105 |
115 |
--------------------------------------------------------------------------------
/src/store/modules/material.js:
--------------------------------------------------------------------------------
1 | import util from '../../libs/util';
2 |
3 | const material = {
4 | state: {
5 | // 当前选中的模版
6 | currentTemplate: null,
7 | // 当前选中的 group
8 | curId: -1,
9 | // 当前选中的 v-image
10 | config: {
11 | x: 0,
12 | y: 0,
13 | rotation: 0,
14 | scale: 0,
15 | src: ''
16 | },
17 | // 拖动定位
18 | dragStartX: 0,
19 | dragStartY: 0
20 | },
21 | mutations: {
22 | // 获取当前使用的模版配置
23 | getCurrentTepmlate(state, config) {
24 | state.currentTemplate = config;
25 | },
26 | // 选中的 v-image 配置与表单同步
27 | setImageId(state, id) {
28 | state.curId = id
29 | state.currentTemplate.configList.group.forEach(item => {
30 | if (item.uniqueId === id) {
31 | state.config = item.vImage
32 | }
33 | })
34 | },
35 | // 创建素材-更换图片
36 | setUrl(state, img) {
37 | const url = img.src;
38 | const width = img.width;
39 | const height = img.height;
40 | const originWidth = width;
41 | const originHeight = height;
42 | const face_x = face_x ? face_x : width / 2;
43 | const face_y = face_y ? face_y : height / 2;
44 |
45 | // 替换模板中的group图片
46 | state.currentTemplate.configList.group.forEach(item => {
47 | if (item.uniqueId === state.curId) {
48 | item.vImage.src = url;
49 |
50 | // 设置图片大小填充group框
51 | let _img = util.newImage(url);
52 | _img.onload = function () {
53 | // 图片宽高比
54 | const perImg = parseFloat((_img.width / _img.height).toFixed(2));
55 | // group 框宽高比
56 | const perGroup = parseFloat((item.width / item.height).toFixed(2));
57 |
58 | if (perImg >= perGroup) {
59 | item.vImage.height = item.height;
60 | item.vImage.width = Math.ceil(item.height * perImg);
61 |
62 | // 图片缩放比例 x
63 | const perX = parseFloat((item.vImage.width / originWidth).toFixed(2));
64 | // group 框中心点 x
65 | const middleX = parseFloat((item.width / 2).toFixed(2));
66 | // 图片缩放之后的人脸坐标
67 | const faceX = parseFloat((face_x * perX).toFixed(2));
68 | // 人脸居中:修改图片坐标 face_x
69 | item.vImage.x = Math.floor(middleX - faceX);
70 | if (item.vImage.x > 0) {
71 | item.vImage.x = 0;
72 | } else if (item.vImage.x < item.width - item.vImage.width) {
73 | item.vImage.x = item.width - item.vImage.width;
74 | }
75 | } else if (perImg < perGroup) {
76 | item.vImage.width = item.width;
77 | item.vImage.height = Math.ceil(item.width / perImg);
78 |
79 | // 图片缩放比例 y
80 | const perY = parseFloat((item.vImage.height / originHeight).toFixed(2));
81 | // group 框中心点 x
82 | const middleY = parseFloat((item.height / 2).toFixed(2));
83 | // 图片缩放之后的人脸坐标
84 | const faceY = parseFloat((face_y * perY).toFixed(2));
85 | // 人脸居中:修改图片坐标 face_y
86 | item.vImage.y = Math.floor(middleY - faceY);
87 | if (item.vImage.y > 0) {
88 | item.vImage.y = 0;
89 | } else if (item.vImage.y < item.height - item.vImage.height) {
90 | item.vImage.y = item.height - item.vImage.height;
91 | }
92 | }
93 | item.vImage.img = _img;
94 | }
95 | }
96 | });
97 | },
98 | start(state, {
99 | x,
100 | y
101 | }) {
102 | state.dragStartX = x - state.config.x;
103 | state.dragStartY = y - state.config.y;
104 | },
105 | move(state, {
106 | x,
107 | y
108 | }) {
109 | state.config.x = x - state.dragStartX;
110 | state.config.y = y - state.dragStartY;
111 | },
112 | end(state, {
113 | x,
114 | y
115 | }) {
116 | state.config.x = x - state.dragStartX;
117 | state.config.y = y - state.dragStartY;
118 | }
119 | }
120 | }
121 |
122 | export default material
--------------------------------------------------------------------------------
/src/components/TemplateStage/LayerGroup.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
63 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
136 |
--------------------------------------------------------------------------------
/src/components/MaterialStage/LayerGroup.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
67 |
90 |
91 |
92 |
93 |
94 |
95 |
132 |
--------------------------------------------------------------------------------
/src/components/CommonSliderBar.vue:
--------------------------------------------------------------------------------
1 |
2 |
23 |
24 |
25 |
106 |
107 |
195 |
--------------------------------------------------------------------------------
/src/components/Material.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
当前模板: {{ currentTemplate.name }}
15 |
由于当前只存储一个模板,默认选择这个模板。
16 |
17 |
18 |
19 |
编辑图片
20 |
21 |
22 |
23 | {{ currentTemplate.name }}
24 |
25 |
26 | 预览(注意:预览时不可编辑图片,编辑之前请先关闭预览)
27 |
28 |
29 |
35 |
36 |
37 |
38 |
39 |
40 |
![]()
41 |
42 |
43 |
44 |
45 |
129 |
130 |
198 |
--------------------------------------------------------------------------------
/src/components/Template.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | 模版名称
8 |
9 |
19 |
20 |
21 |
22 | 画布配置
23 |
24 |
40 |
41 |
42 |
43 | 模版框配置
44 |
45 |
46 |
47 |
48 |
49 | 装饰配置
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
158 |
159 |
186 |
--------------------------------------------------------------------------------
/src/components/TemplateConfig.vue:
--------------------------------------------------------------------------------
1 |
2 |
91 |
92 |
93 |
191 |
--------------------------------------------------------------------------------