├── pages
├── Demo
│ ├── Demo.json
│ ├── Demo.wxss
│ ├── Demo.wxml
│ └── Demo.js
├── detail
│ ├── detail.json
│ ├── detail.wxss
│ ├── detail.wxml
│ └── detail.js
├── example
│ ├── example.json
│ ├── example.wxml
│ ├── example.wxss
│ └── example.js
└── gif
│ ├── gif.json
│ ├── gif.wxml
│ ├── gif.wxss
│ └── gif.js
├── images
├── cancel.png
├── focus.gif
├── example.png
├── choseImg-bg.png
└── sticker
│ ├── 1.png
│ ├── 10.png
│ ├── 11.png
│ ├── 2.png
│ ├── 3.png
│ ├── 4.png
│ ├── 5.png
│ ├── 6.png
│ ├── 7.png
│ ├── 8.png
│ └── 9.png
├── README.md
├── app.json
├── utils
└── util.js
├── project.config.json
├── app.js
└── app.wxss
/pages/Demo/Demo.json:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/pages/detail/detail.json:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/pages/example/example.json:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/pages/gif/gif.json:
--------------------------------------------------------------------------------
1 | {
2 | "disableScroll": true
3 |
4 | }
--------------------------------------------------------------------------------
/images/cancel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlianOL/wx_sticker/HEAD/images/cancel.png
--------------------------------------------------------------------------------
/images/focus.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlianOL/wx_sticker/HEAD/images/focus.gif
--------------------------------------------------------------------------------
/images/example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlianOL/wx_sticker/HEAD/images/example.png
--------------------------------------------------------------------------------
/images/choseImg-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlianOL/wx_sticker/HEAD/images/choseImg-bg.png
--------------------------------------------------------------------------------
/images/sticker/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlianOL/wx_sticker/HEAD/images/sticker/1.png
--------------------------------------------------------------------------------
/images/sticker/10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlianOL/wx_sticker/HEAD/images/sticker/10.png
--------------------------------------------------------------------------------
/images/sticker/11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlianOL/wx_sticker/HEAD/images/sticker/11.png
--------------------------------------------------------------------------------
/images/sticker/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlianOL/wx_sticker/HEAD/images/sticker/2.png
--------------------------------------------------------------------------------
/images/sticker/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlianOL/wx_sticker/HEAD/images/sticker/3.png
--------------------------------------------------------------------------------
/images/sticker/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlianOL/wx_sticker/HEAD/images/sticker/4.png
--------------------------------------------------------------------------------
/images/sticker/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlianOL/wx_sticker/HEAD/images/sticker/5.png
--------------------------------------------------------------------------------
/images/sticker/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlianOL/wx_sticker/HEAD/images/sticker/6.png
--------------------------------------------------------------------------------
/images/sticker/7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlianOL/wx_sticker/HEAD/images/sticker/7.png
--------------------------------------------------------------------------------
/images/sticker/8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlianOL/wx_sticker/HEAD/images/sticker/8.png
--------------------------------------------------------------------------------
/images/sticker/9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlianOL/wx_sticker/HEAD/images/sticker/9.png
--------------------------------------------------------------------------------
/pages/detail/detail.wxss:
--------------------------------------------------------------------------------
1 | /* pages/detail/detail.wxss */
2 |
3 | image{
4 | width: 320px;
5 | height: 178px;
6 | margin: 100rpx auto;
7 | }
--------------------------------------------------------------------------------
/pages/detail/detail.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # sticker
2 | 微信小程序实现图片贴纸效果
3 |
4 | 实现了贴纸叠加、删除、单指移动、右下角缩放旋转的功能
5 |
6 | 这个Demo并没有进行边缘判断
7 | 没有使用最新的movable-view
8 | 部分代码参考了大佬的方法https://blog.csdn.net/qq_37942845/article/details/80169907
9 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "pages": [
3 | "pages/gif/gif",
4 | "pages/Demo/Demo",
5 | "pages/example/example",
6 |
7 |
8 | "pages/detail/detail"
9 |
10 | ],
11 | "window": {
12 |
13 | "backgroundTextStyle": "light",
14 | "navigationBarBackgroundColor": "#fff",
15 | "navigationBarTitleText": "canvas实现贴图",
16 | "navigationBarTextStyle": "black"
17 | }
18 | }
--------------------------------------------------------------------------------
/pages/Demo/Demo.wxss:
--------------------------------------------------------------------------------
1 | /* pages/Demo/Demo.wxss */
2 | .back-container {
3 | display: flex;
4 | flex-direction: column;
5 | width: 100%;
6 | height: 100%;
7 | }
8 | .area-container {
9 | width: 750rpx;
10 | height: 750rpx;
11 | }
12 | .top-container {
13 |
14 | position: absolute;
15 | width: 750rpx;
16 | height: 750rpx;
17 | background-color: #ddd;
18 | z-index:999;
19 | }
20 | .areaview {
21 | width: 200rpx;
22 | height: 200rpx;
23 | }
--------------------------------------------------------------------------------
/utils/util.js:
--------------------------------------------------------------------------------
1 | const formatTime = date => {
2 | const year = date.getFullYear()
3 | const month = date.getMonth() + 1
4 | const day = date.getDate()
5 | const hour = date.getHours()
6 | const minute = date.getMinutes()
7 | const second = date.getSeconds()
8 |
9 | return [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':')
10 | }
11 |
12 | const formatNumber = n => {
13 | n = n.toString()
14 | return n[1] ? n : '0' + n
15 | }
16 |
17 | module.exports = {
18 | formatTime: formatTime
19 | }
20 |
--------------------------------------------------------------------------------
/project.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "项目配置文件。",
3 | "setting": {
4 | "urlCheck": true,
5 | "es6": true,
6 | "postcss": true,
7 | "minified": true,
8 | "newFeature": true
9 | },
10 | "compileType": "miniprogram",
11 | "libVersion": "1.9.90",
12 | "appid": "wxb7ce775b75516de5",
13 | "projectname": "canvas%E5%AE%9E%E7%8E%B0%E8%B4%B4%E5%9B%BE",
14 | "condition": {
15 | "search": {
16 | "current": -1,
17 | "list": []
18 | },
19 | "conversation": {
20 | "current": -1,
21 | "list": []
22 | },
23 | "miniprogram": {
24 | "current": -1,
25 | "list": []
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/pages/Demo/Demo.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/pages/Demo/Demo.js:
--------------------------------------------------------------------------------
1 | // pages/Demo/Demo.js
2 | Page({
3 |
4 | /**
5 | * 页面的初始数据
6 | */
7 | data: {
8 |
9 | },
10 |
11 | /**
12 | * 生命周期函数--监听页面加载
13 | */
14 | onLoad: function (options) {
15 |
16 | },
17 |
18 | /**
19 | * 生命周期函数--监听页面初次渲染完成
20 | */
21 | onReady: function () {
22 |
23 | },
24 |
25 | /**
26 | * 生命周期函数--监听页面显示
27 | */
28 | onShow: function () {
29 |
30 | },
31 |
32 | /**
33 | * 生命周期函数--监听页面隐藏
34 | */
35 | onHide: function () {
36 |
37 | },
38 |
39 | /**
40 | * 生命周期函数--监听页面卸载
41 | */
42 | onUnload: function () {
43 |
44 | },
45 |
46 | /**
47 | * 页面相关事件处理函数--监听用户下拉动作
48 | */
49 | onPullDownRefresh: function () {
50 |
51 | },
52 |
53 | /**
54 | * 页面上拉触底事件的处理函数
55 | */
56 | onReachBottom: function () {
57 |
58 | },
59 |
60 | /**
61 | * 用户点击右上角分享
62 | */
63 | onShareAppMessage: function () {
64 |
65 | }
66 | })
--------------------------------------------------------------------------------
/pages/detail/detail.js:
--------------------------------------------------------------------------------
1 | // pages/detail/detail.js
2 | Page({
3 |
4 | /**
5 | * 页面的初始数据
6 | */
7 | data: {
8 |
9 | },
10 |
11 | /**
12 | * 生命周期函数--监听页面加载
13 | */
14 | onLoad: function (options) {
15 | var path = options.path;
16 | this.setData({
17 | path:path
18 | })
19 |
20 | },
21 |
22 | /**
23 | * 生命周期函数--监听页面初次渲染完成
24 | */
25 | onReady: function () {
26 |
27 | },
28 |
29 | /**
30 | * 生命周期函数--监听页面显示
31 | */
32 | onShow: function () {
33 |
34 | },
35 |
36 | /**
37 | * 生命周期函数--监听页面隐藏
38 | */
39 | onHide: function () {
40 |
41 | },
42 |
43 | /**
44 | * 生命周期函数--监听页面卸载
45 | */
46 | onUnload: function () {
47 |
48 | },
49 |
50 | /**
51 | * 页面相关事件处理函数--监听用户下拉动作
52 | */
53 | onPullDownRefresh: function () {
54 |
55 | },
56 |
57 | /**
58 | * 页面上拉触底事件的处理函数
59 | */
60 | onReachBottom: function () {
61 |
62 | },
63 |
64 | /**
65 | * 用户点击右上角分享
66 | */
67 | onShareAppMessage: function () {
68 |
69 | }
70 | })
--------------------------------------------------------------------------------
/app.js:
--------------------------------------------------------------------------------
1 | //app.js
2 | App({
3 | onLaunch: function () {
4 | // 展示本地存储能力
5 | var logs = wx.getStorageSync('logs') || []
6 | logs.unshift(Date.now())
7 | wx.setStorageSync('logs', logs)
8 |
9 | // 登录
10 | wx.login({
11 | success: res => {
12 | // 发送 res.code 到后台换取 openId, sessionKey, unionId
13 | }
14 | })
15 | // 获取用户信息
16 | wx.getSetting({
17 | success: res => {
18 | if (res.authSetting['scope.userInfo']) {
19 | // 已经授权,可以直接调用 getUserInfo 获取头像昵称,不会弹框
20 | wx.getUserInfo({
21 | success: res => {
22 | // 可以将 res 发送给后台解码出 unionId
23 | this.globalData.userInfo = res.userInfo
24 |
25 | // 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回
26 | // 所以此处加入 callback 以防止这种情况
27 | if (this.userInfoReadyCallback) {
28 | this.userInfoReadyCallback(res)
29 | }
30 | }
31 | })
32 | }
33 | }
34 | })
35 | },
36 | globalData: {
37 | userInfo: null
38 | }
39 | })
--------------------------------------------------------------------------------
/app.wxss:
--------------------------------------------------------------------------------
1 | /**app.wxss**/
2 | image {
3 | width: auto;
4 | height: auto;
5 | }
6 |
7 | .width-full {
8 | width: 100%;
9 | }
10 |
11 | .color-white {
12 | color: #fff;
13 | }
14 |
15 | .flex {
16 | display: box; /* OLD - Android 4.4- */
17 | display: -webkit-box; /* OLD - iOS 6-, Safari 3.1-6 */
18 | display: -moz-box; /* OLD - Firefox 19- (buggy but mostly works) */
19 | display: -ms-flexbox; /* TWEENER - IE 10 */
20 | display: -webkit-flex; /* NEW - Chrome */
21 | display: flex;
22 |
23 | }
24 |
25 | .flex-hc {
26 | /* 09版 */
27 | -webkit-box-pack: center;
28 | /* 12版 */
29 | -webkit-justify-content: center;
30 | -moz-justify-content: center;
31 | -ms-justify-content: center;
32 | -o-justify-content: center;
33 | justify-content: center;
34 | }
35 |
36 | .flex-vc {
37 | /* 09版 */
38 | -webkit-box-align: center;
39 | /* 12版 */
40 | -webkit-align-items: center;
41 | -moz-align-items: center;
42 | -ms-align-items: center;
43 | -o-align-items: center;
44 | align-items: center;
45 | }
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/pages/example/example.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/pages/gif/gif.wxml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/pages/gif/gif.wxss:
--------------------------------------------------------------------------------
1 | /* pages/gif/gif.wxss */
2 |
3 | page {
4 | background-color: #ddd;
5 | }
6 |
7 | .gif-constainer {
8 | width: 150rpx;
9 | height: 150rpx;
10 | }
11 |
12 | .back-container {
13 | width: 750rpx;
14 | height: 750rpx;
15 | background-color: #888;
16 | }
17 |
18 | .bottom-container {
19 | height: 500rpx;
20 | }
21 |
22 | .image-container {
23 | width: 100rpx;
24 | height: 100rpx;
25 | }
26 |
27 | .image.cancel {
28 | position: absolute;
29 | top: -15rpx;
30 | left: -15rpx;
31 | width: 30rpx;
32 | height: 30rpx;
33 | z-index: 30;
34 | }
35 |
36 | .touchWrap {
37 | transform-origin: center;
38 | position: absolute;
39 | z-index: 100;
40 | width: 200rpx;
41 | height: 200rpx;
42 | }
43 |
44 | .imgWrap {
45 | box-sizing: border-box;
46 | width: 100%;
47 | transform-origin: center;
48 | float: left;
49 | border: 5rpx transparent dashed;
50 | }
51 |
52 | .x {
53 | position: absolute;
54 | top: 0rpx;
55 | left: 0rpx;
56 | width: 30rpx;
57 | height: 30rpx;
58 | z-index: 30;
59 | }
60 |
61 | .o {
62 | position: absolute;
63 | bottom: 0rpx;
64 | right: 0rpx;
65 | width: 30rpx;
66 | height: 30rpx;
67 | z-index: 30;
68 | }
69 |
70 | .recommend_scroll_x_box {
71 | position: relative;
72 | display: flex;
73 | flex-direction: row;
74 | background-color: #987;
75 | height: 150rpx;
76 | /* padding-top: 30rpx;
77 | padding-bottom: 40rpx; */
78 | width: 100%;
79 | overflow: auto;
80 | white-space: nowrap;
81 | /* display: flex; */
82 | vertical-align: top;
83 | }
84 |
85 | .sticker-lists image {
86 | width: 100rpx;
87 | height: 100rpx;
88 | }
89 |
90 | .sticker-lists {
91 | margin: 20rpx 20rpx 20rpx 20rpx;
92 | width: 100rpx;
93 | height: 100rpx;
94 | display: inline-block;
95 | align-items: center;
96 | align-content: center;
97 | align-self: center;
98 | }
99 |
--------------------------------------------------------------------------------
/pages/example/example.wxss:
--------------------------------------------------------------------------------
1 | /* pages/uploadImg/uploadImg.wxss */
2 | page {
3 | height: 100%;
4 | }
5 | .top-box {
6 | padding: 20rpx 30rpx;
7 | height: calc(100% - 182px);
8 | display: flex;
9 | align-items: center;
10 | justify-content: center;
11 | }
12 |
13 | .original-img {
14 | width: 320px;
15 | height: 178px;
16 | margin: 0 auto;
17 | }
18 |
19 | .bg-img {
20 | position: absolute;
21 | top: 0;
22 | right: 0;
23 | z-index: -1;
24 | }
25 |
26 | .bottom {
27 | position: fixed;
28 | bottom: 0;
29 | left: 0;
30 | width: 100%;
31 | background-color: rgba(230,225,225,0.8);
32 | }
33 |
34 | .bottom>view.tab {
35 | padding-right: 30rpx;
36 | padding-left: 30rpx;
37 | }
38 |
39 | .bottom>view.sticker-lists-body{
40 | padding-left: 30rpx;
41 | }
42 |
43 | .recommend_scroll_x_box {
44 | height: 100rpx;
45 | padding-top: 30rpx;
46 | padding-bottom: 40rpx;
47 | width: 100%;
48 | overflow: auto;
49 | white-space: nowrap;
50 | display: flex;
51 | vertical-align: top;
52 | }
53 |
54 | ::-webkit-scrollbar {
55 | width: 0;
56 | height: 0;
57 | color: transparent;
58 | }
59 |
60 | .sticker-lists-body {
61 | padding-left: 30rpx;
62 | }
63 |
64 | .sticker-lists-body .sticker-list {
65 | width: 100rpx;
66 | height: 100rpx;
67 | margin-right: 24rpx;
68 | display: inline-block;
69 | vertical-align: top;
70 | }
71 | .sticker-lists-body .sticker-list image {
72 | width: 100rpx;
73 | height: 100rpx;
74 | background-color: #ffffff;
75 | }
76 |
77 | .bottom image {
78 | width: 50rpx;
79 | height: 50rpx;
80 | }
81 |
82 | .bottom .tab image {
83 | margin-right: 60rpx;
84 | }
85 |
86 | .bottom .tab {
87 | padding: 25rpx 60rpx;
88 | background-color: #f4f4f4;
89 | height: 70rpx;
90 | }
91 |
92 | .bottom .tab .tab-list {
93 | position: relative;
94 | float: left;
95 | display: flex;
96 | align-items: center;
97 | margin-top: 10rpx;
98 | }
99 |
100 | .bottom .tab button{
101 | background-color: #d81e06;
102 | float: right;
103 | height: 70rpx;
104 | line-height: 70rpx;
105 | font-size: 30rpx;
106 | }
107 |
108 | .bottom .tab button.color-red {
109 | background-color: #fff;
110 | border: 1rpx solid #d81e06;
111 | margin-right: 10rpx;
112 | }
113 |
114 | .bottom .tab .tab-list image.active::before {
115 | content: '';
116 | position: absolute;
117 | top: -50rpx;
118 | left: 5rpx;
119 | border-right: 20rpx solid transparent;
120 | border-left: 20rpx solid transparent;
121 | border-bottom: 20rpx solid #f4f4f4;
122 | }
123 |
124 |
125 |
126 |
127 |
128 | movable-view {
129 | height: 50px;
130 | width: 50px;
131 | }
132 |
133 | movable-view .sticker-box {
134 | position: relative;
135 | width:100%;
136 | height: 100%;
137 | border: 1rpx dashed #ccc;
138 | }
139 |
140 | image.cancel {
141 | position: absolute;
142 | top: -15rpx;
143 | left: -15rpx;
144 | width:30rpx;
145 | height: 30rpx;
146 | z-index: 30;
147 | }
148 |
149 | .canvas-box {
150 | opacity: 0;
151 | position: fixed;
152 | top: 100%;
153 | left: 0;
154 | z-index: -1;
155 | }
156 |
157 | .canvas-box canvas {
158 | opacity: 0;
159 | }
--------------------------------------------------------------------------------
/pages/gif/gif.js:
--------------------------------------------------------------------------------
1 | // pages/gif/gif.js
2 | var items = new Array();
3 | var index = null;
4 | Page({
5 |
6 | /**
7 | * 页面的初始数据
8 | */
9 | data: {
10 | stickers: [
11 | '../../images/sticker/1.png',
12 | '../../images/sticker/2.png',
13 | '../../images/sticker/3.png',
14 | '../../images/sticker/4.png',
15 | '../../images/sticker/5.png',
16 | '../../images/sticker/6.png',
17 | '../../images/sticker/7.png',
18 | '../../images/sticker/8.png',
19 | '../../images/sticker/9.png',
20 | '../../images/sticker/10.png',
21 | '../../images/sticker/11.png'],
22 | // itemList: [{
23 | // id: 1,
24 | // image: '../../images/sticker/1.png',//图片地址
25 | // top: 100,//初始图片的位置
26 | // left: 100,
27 | // x: 150, //初始圆心位置,可再downImg之后又宽高和初始的图片位置得出
28 | // y: 150,
29 | // scale: 1,//缩放比例 1为不缩放
30 | // angle: 0,//旋转角度
31 | // active: false, //判定点击状态
32 | // width: 100,
33 | // height: 100
34 | // }, {
35 | // id: 2,
36 | // image: '../../images/sticker/2.png',
37 | // top: 50,
38 | // left: 50,
39 | // x: 100,
40 | // y: 100,
41 | // scale: 1,
42 | // angle: 0,
43 | // active: false,
44 | // width: 100,
45 | // height:100
46 | // }],
47 |
48 | },
49 | WraptouchStart: function (e) {
50 | for (let i = 0; i < items.length; i++) { //旋转数据找到点击的
51 | items[i].active = false;
52 | if (e.currentTarget.dataset.id == items[i].id) {
53 | index = i; //记录下标
54 | items[index].active = true; //开启点击属性
55 | }
56 | }
57 |
58 | items[index].lx = e.touches[0].clientX; // 记录点击时的坐标值
59 | items[index].ly = e.touches[0].clientY;
60 | this.setData({ //赋值
61 | itemList: items
62 | })
63 | }
64 | , WraptouchMove: function (e) {
65 | //移动时的坐标值也写图片的属性里
66 | items[index]._lx = e.touches[0].clientX;
67 | items[index]._ly = e.touches[0].clientY;
68 |
69 | //追加改动值
70 | items[index].left += items[index]._lx - items[index].lx; // x方向
71 | items[index].top += items[index]._ly - items[index].ly; // y方向
72 | items[index].x += items[index]._lx - items[index].lx;
73 | items[index].y += items[index]._ly - items[index].ly;
74 |
75 | //把新的值赋给老的值
76 | items[index].lx = e.touches[0].clientX;
77 | items[index].ly = e.touches[0].clientY;
78 | this.setData({//赋值就移动了
79 | itemList: items
80 | })
81 | },
82 | WraptouchEnd:function(e) {
83 |
84 | },
85 | // 触摸开始事件 items是this.data.itemList的全局变量,便于赋值 所有的值都应给到对应的对象里
86 | touchStart: function (e) {
87 | //找到点击的那个图片对象,并记录
88 | for (let i = 0; i < items.length; i++) {
89 | items[i].active = false;
90 |
91 | if (e.currentTarget.dataset.id == items[i].id) {
92 | console.log('e.currentTarget.dataset.id', e.currentTarget.dataset.id)
93 | index = i;
94 | console.log(items[index])
95 | items[index].active = true;
96 | }
97 | }
98 | //获取作为移动前角度的坐标
99 | items[index].tx = e.touches[0].clientX;
100 | items[index].ty = e.touches[0].clientY;
101 | //移动前的角度
102 | items[index].anglePre = this.countDeg(items[index].x, items[index].y, items[index].tx, items[index].ty)
103 | console.log("移动前的角度", items[index].anglePre)
104 | //获取图片半径
105 | items[index].r = this.getDistancs(items[index].x, items[index].y, items[index].left, items[index].top)-20;//20是右下角移动图片到本图边缘的估计值,因为这个获取半径的方法跟手指的位置有关
106 | console.log("半径", items[index].r);
107 | },
108 |
109 | // 触摸移动事件
110 | touchMove: function (e) {
111 | //记录移动后的位置
112 | items[index]._tx = e.touches[0].clientX;
113 | items[index]._ty = e.touches[0].clientY;
114 | //移动的点到圆心的距离
115 | var width = wx.getSystemInfoSync().windowWidth
116 | items[index].disPtoO = this.getDistancs(items[index].x, items[index].y, items[index]._tx - width * 0.125, items[index]._ty - 10)
117 |
118 | items[index].scale = items[index].disPtoO / items[index].r; //手指滑动的点到圆心的距离与半径的比值作为图片的放大比例
119 | items[index].oScale = 1 / items[index].scale;//图片放大响应的右下角按钮同比缩小
120 |
121 | //移动后位置的角度
122 | items[index].angleNext = this.countDeg(items[index].x, items[index].y, items[index]._tx, items[index]._ty)
123 | //角度差
124 | items[index].new_rotate = items[index].angleNext - items[index].anglePre;
125 |
126 | //叠加的角度差
127 | items[index].rotate += items[index].new_rotate;
128 | items[index].angle = items[index].rotate; //赋值
129 |
130 | //用过移动后的坐标赋值为移动前坐标
131 | items[index].tx = e.touches[0].clientX;
132 | items[index].ty = e.touches[0].clientY;
133 | items[index].anglePre = this.countDeg(items[index].x, items[index].y, items[index].tx, items[index].ty)
134 | items[index].angle = items[index].anglePre-135;
135 |
136 | //赋值setData渲染
137 | this.setData({
138 | "itemList": items
139 | })
140 | },
141 | deleteItem:function(e){
142 | console.log("删除按钮:",e)
143 | for (let i = 0; i < items.length; i++) {
144 |
145 |
146 | if (e.currentTarget.dataset.id == items[i].id) {
147 | items.splice(i, 1);
148 | this.setData({
149 | "itemList": items
150 | })
151 | }
152 | }
153 | },
154 | /**
155 | * 生命周期函数--监听页面加载
156 | */
157 | onLoad: function (options) {
158 | // items = this.data.itemList;
159 | // this.setData({
160 | // "itemList": items
161 | // })
162 | },
163 | changeImg:function(e){
164 |
165 |
166 | var temDic = {
167 | id: e.currentTarget.dataset.index+1,
168 | image: e.currentTarget.dataset.url,//图片地址
169 | top: 100,//初始图片的位置
170 | left: 100,
171 | x: 150, //初始圆心位置,可再downImg之后又宽高和初始的图片位置得出
172 | y: 150,
173 | scale: 1,//缩放比例 1为不缩放
174 | angle: 0,//旋转角度
175 | active: false, //判定点击状态
176 | width: 100,
177 | height: 100
178 | }
179 | items.push(temDic)
180 | this.setData({
181 | "itemList": items
182 | })
183 | },
184 | /**
185 | * 生命周期函数--监听页面初次渲染完成
186 | */
187 | onReady: function () {
188 |
189 | },
190 |
191 | /**
192 | * 生命周期函数--监听页面显示
193 | */
194 | onShow: function () {
195 |
196 | },
197 |
198 | /**
199 | * 生命周期函数--监听页面隐藏
200 | */
201 | onHide: function () {
202 |
203 | },
204 |
205 | /**
206 | * 生命周期函数--监听页面卸载
207 | */
208 | onUnload: function () {
209 |
210 | },
211 |
212 | /**
213 | * 页面相关事件处理函数--监听用户下拉动作
214 | */
215 | onPullDownRefresh: function () {
216 |
217 | },
218 |
219 | /**
220 | * 页面上拉触底事件的处理函数
221 | */
222 | onReachBottom: function () {
223 |
224 | },
225 |
226 | /**
227 | * 用户点击右上角分享
228 | */
229 | onShareAppMessage: function () {
230 |
231 | },
232 | /*
233 | *参数1和2为图片圆心坐标
234 | *参数3和4为手点击的坐标
235 | *返回值为手点击的坐标到圆心的角度
236 | */
237 | countDeg: function (cx, cy, pointer_x, pointer_y) {
238 | var ox = pointer_x - cx;
239 | var oy = pointer_y - cy;
240 | var to = Math.abs(ox / oy);
241 | var angle = Math.atan(to) / (2 * Math.PI) * 360;//鼠标相对于旋转中心的角度
242 |
243 | if (ox < 0 && oy < 0)//相对在左上角,第四象限,js中坐标系是从左上角开始的,这里的象限是正常坐标系
244 | {
245 | angle = -angle;
246 | } else if (ox <= 0 && oy >= 0)//左下角,3象限
247 | {
248 | angle = -(180 - angle)
249 | } else if (ox > 0 && oy < 0)//右上角,1象限
250 | {
251 | angle = angle;
252 | } else if (ox > 0 && oy > 0)//右下角,2象限
253 | {
254 | angle = 180 - angle;
255 | }
256 |
257 | return angle;
258 | },
259 | //计算触摸点到圆心的距离
260 | getDistancs(cx, cy, pointer_x, pointer_y) {
261 | var ox = pointer_x - cx;
262 | var oy = pointer_y - cy;
263 | return Math.sqrt(
264 | ox * ox + oy * oy
265 | );
266 | }
267 | })
--------------------------------------------------------------------------------
/pages/example/example.js:
--------------------------------------------------------------------------------
1 | // pages/uploadImg/uploadImg.js
2 | Page({
3 |
4 | /**
5 | * 页面的初始数据
6 | */
7 | data: {
8 | imgUrl: '../../images/example.png',
9 | stickers: ['../../images/sticker/1.png',
10 | '../../images/sticker/2.png',
11 | '../../images/sticker/3.png',
12 | '../../images/sticker/4.png',
13 | '../../images/sticker/5.png',
14 | '../../images/sticker/6.png',
15 | '../../images/sticker/7.png',
16 | '../../images/sticker/8.png',
17 | '../../images/sticker/9.png',
18 | '../../images/sticker/10.png',
19 | '../../images/sticker/11.png'],
20 |
21 | x: 160,
22 | y: 50,
23 | chosedImg: false,
24 | stv: {
25 | offsetX: 160,
26 | offsetY: 50,
27 | zoom: false, //是否缩放状态
28 | distance: 0, //两指距离
29 | scale: 1, //缩放倍数
30 | width: 50,
31 | height: 50,
32 | },
33 | },
34 | // 第一张贴图触摸开始
35 | touchstartCallback: function (e) {
36 | //console.log('touchstartCallback');
37 | //console.log(e);
38 | if (e.touches.length === 1) {
39 | let { clientX, clientY } = e.touches[0];
40 | this.startX = clientX;
41 | this.startY = clientY;
42 | this.touchStartEvent = e.touches;
43 | } else {
44 | let xMove = e.touches[1].clientX - e.touches[0].clientX;
45 | let yMove = e.touches[1].clientY - e.touches[0].clientY;
46 | let distance = Math.sqrt(xMove * xMove + yMove * yMove);
47 | this.setData({
48 | 'stv.distance': distance,
49 | 'stv.zoom': true, //缩放状态
50 | })
51 | }
52 | },
53 |
54 | // 第一张贴图触摸移动中
55 | touchmoveCallback: function (e) {
56 | //console.log('touchmoveCallback');
57 | //console.log(e);
58 | if (e.touches.length === 1) {
59 | //单指移动
60 | if (this.data.stv.zoom) {
61 | //缩放状态,不处理单指
62 | return;
63 | }
64 | let { clientX, clientY } = e.touches[0];
65 | let offsetX = clientX - this.startX;
66 | let offsetY = clientY - this.startY;
67 | this.startX = clientX;
68 | this.startY = clientY;
69 | let { stv } = this.data;
70 | stv.offsetX += offsetX;
71 | stv.offsetY += offsetY;
72 | stv.offsetLeftX = -stv.offsetX;
73 | stv.offsetLeftY = -stv.offsetLeftY;
74 | var nowWidth = this.data.stv.width;
75 | var maxoffsetX = 320 - nowWidth;
76 | var nowHeight = this.data.stv.height;
77 | var maxoffsetY = 178.125 - nowHeight;
78 |
79 | if (stv.offsetX > maxoffsetX) {
80 | stv.offsetX = maxoffsetX;
81 | } else if (stv.offsetX < 0) {
82 | stv.offsetX = 0;
83 | }
84 | if (stv.offsetY > maxoffsetY) {
85 | stv.offsetY = maxoffsetY;
86 | } else if (stv.offsetY < 0) {
87 | stv.offsetY = 0;
88 | }
89 | this.setData({
90 | stv: stv
91 | });
92 |
93 | } else {
94 | //双指缩放
95 | let xMove = e.touches[1].clientX - e.touches[0].clientX;
96 | let yMove = e.touches[1].clientY - e.touches[0].clientY;
97 | let distance = Math.sqrt(xMove * xMove + yMove * yMove);
98 |
99 | let distanceDiff = distance - this.data.stv.distance;
100 | let newScale = this.data.stv.scale + 0.005 * distanceDiff;
101 | if (newScale < 0.5) {
102 | newScale = 0.5;
103 | }
104 | if (newScale > 4) {
105 | newScale = 4;
106 | }
107 | let newWidth = newScale * 50;
108 | let newHeight = newScale * 50;
109 |
110 | this.setData({
111 | 'stv.distance': distance,
112 | 'stv.scale': newScale,
113 | 'stv.width': newWidth,
114 | 'stv.height': newWidth,
115 | })
116 | //console.log(this.data.stv.scale)
117 | }
118 | },
119 |
120 | // 第一张贴图触摸结束
121 | touchendCallback: function (e) {
122 | // console.log('touchendCallback');
123 | //console.log(e);
124 | if (e.touches.length === 0) {
125 | this.setData({
126 | 'stv.zoom': false, //重置缩放状态
127 | })
128 | }
129 | },
130 |
131 | //切换贴纸
132 | changeImg: function (e) {
133 | var $img = e.currentTarget.dataset.url;
134 | var chosedImg = this.data.chosedImg;
135 | this.setData({
136 | x: 160,
137 | y: 50,
138 | stv: {
139 | offsetX: 160,
140 | offsetY: 50,
141 | zoom: false, //是否缩放状态
142 | distance: 0, //两指距离
143 | scale: 1, //缩放倍数
144 | width: 50,
145 | height: 50,
146 | },
147 | chosedImg: $img,
148 | })
149 | },
150 |
151 | //取消贴纸
152 | cancel: function () {
153 | this.setData({
154 | chosedImg: false,
155 | x: 150,
156 | y: 75,
157 | stv: {
158 | offsetX: 75,
159 | offsetY: 75,
160 | zoom: false, //是否缩放状态
161 | distance: 0, //两指距离
162 | scale: 1, //缩放倍数
163 | width: 50,
164 | height: 50,
165 | }
166 | })
167 | },
168 |
169 | //将贴纸绘制到canvas的固定
170 | setSticker: function (context) {
171 | var Sticker = this.data.chosedImg;
172 | var newtop = this.data.stv.offsetX * 2;
173 | var newleft = this.data.stv.offsetY * 2;
174 | var newswidth = this.data.stv.width * 2;
175 | var newheight = this.data.stv.height * 2;
176 | context.drawImage(Sticker, newtop, newleft, newswidth, newheight)
177 | context.save();
178 | context.restore();
179 | context.stroke();
180 | },
181 |
182 | //将canvas转换为图片保存到本地,然后将图片路径传给image图片的src
183 | createNewImg: function (imgUrl) {
184 | var that = this;
185 | var chosedImg = this.data.chosedImg;
186 | var path = imgUrl;
187 | var context = wx.createCanvasContext('mycanvas');
188 |
189 | //防止锯齿,绘的图片是所需图片的两倍
190 | context.drawImage(path, 0, 0, 640, 356.266);
191 |
192 | //如果有贴纸则绘制贴纸
193 | if (chosedImg) {
194 | this.setSticker(context);
195 | }
196 | //绘制图片
197 | context.draw();
198 |
199 | //将生成好的图片保存到本地,需要延迟一会,绘制期间耗时
200 | setTimeout(function () {
201 | wx.canvasToTempFilePath({
202 | canvasId: 'mycanvas',
203 | success: function (res) {
204 | var tempFilePath = res.tempFilePath;
205 | console.log(tempFilePath);
206 | that.setData({
207 | imagePath: tempFilePath,
208 | })
209 | wx.navigateTo({
210 | url: '/pages/detail/detail?path=' + tempFilePath,
211 | })
212 | },
213 | fail: function (res) {
214 | console.log(res);
215 | },
216 | complete:function(e){
217 | wx.hideLoading();
218 | }
219 | });
220 | }, 200);
221 |
222 | },
223 |
224 | //点击保存按钮
225 | save: function () {
226 | var that = this;
227 | wx.showLoading({
228 | title: '创建中...',
229 | duration: 10000,
230 | })
231 | var imgUrl = that.data.imgUrl
232 | that.createNewImg(imgUrl);
233 | },
234 |
235 | /**
236 | * 生命周期函数--监听页面加载
237 | */
238 | onLoad: function () {
239 | },
240 |
241 | /**
242 | * 生命周期函数--监听页面初次渲染完成
243 | */
244 | onReady: function () {
245 |
246 | },
247 |
248 | /**
249 | * 生命周期函数--监听页面显示
250 | */
251 | onShow: function () {
252 |
253 | },
254 |
255 | /**
256 | * 生命周期函数--监听页面隐藏
257 | */
258 | onHide: function () {
259 |
260 | },
261 |
262 | /**
263 | * 生命周期函数--监听页面卸载
264 | */
265 | onUnload: function () {
266 |
267 | },
268 |
269 | /**
270 | * 页面相关事件处理函数--监听用户下拉动作
271 | */
272 | onPullDownRefresh: function () {
273 |
274 | },
275 |
276 | /**
277 | * 页面上拉触底事件的处理函数
278 | */
279 | onReachBottom: function () {
280 |
281 | },
282 |
283 | /**
284 | * 用户点击右上角分享
285 | */
286 | onShareAppMessage: function () {
287 |
288 | }
289 | })
--------------------------------------------------------------------------------