微信小程序 canvas 页面布局效果
实现功能:
1、落笔的时候开始计时
2、色卡:点击色卡,选择画笔的颜色
3、文本:文本可以选择格式
4、画笔:画笔粗细和透明度选择
5、把画布生成图片,并上传到服务器
借助mini-color-picker组件实现色卡的选择
<view class="page_view" style="margin-top:{{navBarHeight*2+0}}rpx;">
<canvas canvas-id="myCanvas" class="canvasBox _box-shadow" bindtouchstart="start" bindtouchmove="move"></canvas>
<view class="bottomBox" bindtap="closeDialog">
<view class="brushDialog" hidden="{{brushDialogHidden}}" catchtap="preventevents">
<view class="brushWidthBox">
<image bindtap="checkBrush" data-value="{{item.value}}" wx:for="{{brushList}}" wx:key="index" src="../../images/img/{{item.url}}" mode="heightFix"/>
</view>
<view class="sliderBg" style="margin: 20rpx 30rpx;">
<view class="setBgOpacity" style="background-image: linear-gradient(to right, {{tools.setOpacticy(strokeStyle, 0)}}, {{tools.setOpacticy(strokeStyle, 1)}});"></view>
<slider value="{{globalAlpha}}" class="globalAlphaBox" bindchanging="sliderchange" bindchange="sliderchange" min="0" max="1" step="0.01" block-size="20" activeColor="rgba(255,255,255,0)" backgroundColor="rgba(255,255,255,0)"/>
</view>
</view>
<view class="canvasToll _box-shadow">
<image src="../../images/img/colorSwatches.png" mode="heightFix" bindtap="openColorDialog" />
<image src="../../images/img/text.png" mode="heightFix" bindtap="chengeText" />
<!-- <image src="../../images/img/bucket.png" mode="heightFix"/> -->
<image src="../../images/img/brush.png" mode="heightFix" catchtap="openBrushDialog"/>
</view>
<view class="btnBox">
<button hidden="{{!hiddenFormat}}" class="btn" bindtap="submit">确定完成</button>
</view>
</view>
<input focus="{{focus}}" value="{{inputValue}}" type="text" class="textInput" bindinput="bindKeyInput" bindconfirm="confirmInput" hidden="{{hiddenFormat}}" />
</view>
<view class="countdownBox" style="top:{{navBarHeight*2+100}}rpx;">
<image src="/images/img/alarmClock.png" mode="heightFix"/>{{timerValue.text}}
</view>
<view class="textFormat" hidden="{{hiddenFormat}}" bindtap="openFormat">格式</view>
<view class="maskBackground" wx:if="{{showDialog}}" bindtap="hideMask">
<view class="pickerBox" catchtap="preventevents" style="background-color: {{showDialog == 2 ? '#fff' : '#F4F6F4'}};">
<view class="maskTitle">
<view class="">{{showDialog == 2 ? '格式' : '颜色'}}</view>
<image src="../../images/img/guan.png" mode="widthFix" class="guanImg" bindtap="hideMask" />
</view>
<view wx:if="{{showDialog == 2}}" class="formatContent">
<view wx:for="{{formatList}}" wx:key="index" bindtap="changeFormat" data-value="{{item}}">{{item}}</view>
</view>
<view wx:if="{{showDialog == 1}}" class="colorContent">
<color-picker bindchangeColor="pickColor" initColor="{{strokeStyle}}" />
<view class="opticyLabel">不透明度</view>
<view class="sliderBox">
<view class="sliderBg">
<view class="setBgOpacity" style="background-image: linear-gradient(to right, {{tools.setOpacticy(strokeStyle, 0)}}, {{tools.setOpacticy(strokeStyle, 1)}});"></view>
<slider value="{{globalAlpha}}" class="globalAlphaBoxInColor" bindchanging="sliderchange" bindchange="sliderchange" min="0" max="1" step="0.01" block-size="28" activeColor="rgba(255,255,255,0)" backgroundColor="rgba(255,255,255,0)" />
</view>
<view class="slideValue _box-shadow">{{tools.integer(globalAlpha * 100)}}%</view>
</view>
<view class="colorBottomBox">
<view class="activeColorBox" style="background-color: {{strokeStyle}};opacity: {{globalAlpha}};"></view>
<view class="activeColorArr">
<view wx:for="{{activeColorList}}" wx:key="index" class="activeColor _box-shadow" style="background-color: {{item}}; margin-top: {{index > 4 ? '32rpx' : 0}}" bindtap="checkCorlor" data-color="{{item}}"></view>
<image bindtap="addColor" class="activeColor" src="../../images/img/increase.png" mode="widthFix" style="margin-top: {{activeColorList.length > 4 ? '32rpx' : 0}}"/>
</view>
</view>
</view>
</view>
</view>
<wxs src = "../../utils/tools.wxs" module="tools"></wxs>
/* treatmentPages/drawingCanvas/drawingCanvas.wxss */
.viewPadd {
background-color: #fff;
padding: 0 30rpx;
border-radius: 14rpx;
margin-top: 46rpx;
box-shadow: 0 0 4rpx 6rpx #ededed;
}
.viewHeader {
height: 94rpx;
line-height: 94rpx;
border-bottom: 1rpx solid #DCDCDC;
position: relative;
font-size: 32rpx;
font-weight: bold;
color: #53545F;
padding-left: 48rpx;
}
.viewHeader::before {
content: '';
position: absolute;
top: 50%;
left: 15rpx;
transform: translateY(-50%);
background-color: #007AFF;
width: 10rpx;
height: 30rpx;
border-radius: 5rpx;
border: none;
display: block;
}
.viewContent {
padding: 44rpx 20rpx 100rpx;
color: #666666;
font-size: 30rpx;
line-height: 48rpx;
}
.btnBox {
height: 80rpx;
}
.btn {
background-color: #007AFF;
border-radius: 14rpx;
color: #fff;
font-size: 32rpx;
border: none;
}
.canvasBox {
width: 100%;
height: calc(100vh - 520rpx);
background-color: #fff;
border-radius: 14rpx;
}
.canvasToll {
width: 100%;
background-color: #fff;
height: 124rpx;
border-radius: 14rpx;
margin: 46rpx 0;
display: flex;
justify-content: space-around;
align-items: center;
}
.canvasToll image {
height: 52rpx;
}
.bottomBox {
position: fixed;
bottom: 50rpx;
left: 30rpx;
right: 30rpx;
z-index: 2;
}
.countdownBox {
background-color: #fff;
position: fixed;
right: 0;
top: 400rpx;
border-radius: 30rpx 0 0 30rpx;
display: flex;
justify-content: center;
align-items: center;
width: 176rpx;
height: 60rpx;
color: #666666;
font-size: 24rpx;
box-shadow: -2rpx 0 16rpx 10rpx rgba(144, 138, 131, 0.35);
}
.countdownBox image {
height: 35rpx;
margin-right: 14rpx;
}
.textInput {
position: fixed;
bottom: 50rpx;
left: 30rpx;
right: 30rpx;
height: 80rpx;
z-index: 10;
/* border: none; */
}
.textFormat {
background-color: #fff;
position: fixed;
bottom: 200rpx;
left: 0;
border-radius: 0 30rpx 30rpx 0;
display: flex;
justify-content: center;
align-items: center;
width: 100rpx;
height: 60rpx;
color: #666666;
font-size: 24rpx;
box-shadow: -2rpx 0 16rpx 10rpx rgba(144, 138, 131, 0.35);
z-index: 3;
}
.maskBackground {
background-color: rgba(144, 138, 131, 0.1);
}
.pickerBox {
position: absolute;
width: 100%;
bottom: 0;
left: 0;
box-sizing: border-box;
z-index: 9;
border-radius: 34rpx 34rpx 0 0;
overflow: hidden;
box-shadow: 0rpx 0rpx 4rpx 10rpx #14010128;
max-height: 80%;
overflow-y: auto;
}
.maskTitle {
line-height: 120rpx;
font-weight: bold;
font-size: 40rpx;
padding: 0 30rpx;
display: flex;
align-items: center;
justify-content: space-between;
color: #000000;
}
.guanImg {
width: 51rpx;
}
.formatContent {
display: flex;
align-items: center;
justify-content: space-between;
margin: 50rpx 30rpx 100rpx;
}
.formatContent view {
width: 168rpx;
height: 114rpx;
line-height: 114rpx;
text-align: center;
background-color: #F1F1F6;
font-weight: bold;
}
.formatContent view:nth-child(1) {
border-radius: 20rpx 0 0 20rpx;
}
.formatContent view:nth-child(3) {
text-decoration: underline;
}
.formatContent view:nth-child(4) {
border-radius: 0 20rpx 20rpx 0;
text-decoration: line-through;
}
.brushDialog {
background-color: #fff;
width: 442rpx;
border-radius: 20rpx;
position: absolute;
right: -10rpx;
top: -180rpx;
box-shadow: 2rpx 4rpx 8rpx 6rpx rgba(122, 122, 122, 0.40);
z-index: 3;
}
.brushWidthBox {
display: flex;
justify-content: space-around;
padding: 28rpx 0;
border-bottom: 1rpx solid #C6C6C7;
}
.brushWidthBox image {
height: 46rpx;
}
.globalAlphaBox {
width: 100%;
margin: 0;
}
.brushDialog::after {
content: "";
position: absolute;
bottom: -0rpx;
right: 85rpx;
width: 60rpx;
height: 30rpx;
background-color: #fff;
}
.brushDialog::before {
content: "";
position: absolute;
bottom: -20rpx;
right: 96rpx;
box-shadow: 2rpx 4rpx 8rpx 6rpx rgba(122, 122, 122, 0.40);
border-radius: 6rpx;
width: 40rpx;
height: 40rpx;
background: #fff;
transform: rotate(45deg);
}
.globalAlphaBox .wx-slider-handle-wrapper {
height: 40rpx;
border-radius: 40rpx !important;
}
.globalAlphaBoxInColor {
width: 510rpx;
margin: 0;
}
.globalAlphaBoxInColor .wx-slider-handle-wrapper {
height: 70rpx;
border-radius: 70rpx !important;
}
.sliderBg {
margin: 20rpx 0rpx;
position: relative;
}
.setBgOpacity {
position: absolute;
top: 20rpx;
left: 0;
bottom: 20rpx;
width: 100%;
border-radius: 40rpx;
}
.sliderBox {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 30rpx;
box-sizing: border-box;
}
.opticyLabel {
padding: 70rpx 30rpx 0;
color: #666666;
font-size: 24rpx;
}
.slideValue {
width: 144rpx;
height: 70rpx;
line-height: 70rpx;
text-align: center;
background-color: #fff;
border-radius: 13rpx;
color: #666666;
font-size: 32rpx;
}
.colorBottomBox {
margin: 20rpx 30rpx 80rpx;
padding: 46rpx 30rpx 46rpx 0;
border-top: 1rpx solid #C6C6C7;
display: flex;
justify-content: space-between;
}
.activeColorBox {
width: 146rpx;
height: 146rpx;
border-radius: 16rpx;
}
.activeColorArr {
width: 470rpx;
height: 146rpx;
display: flex;
flex-wrap: wrap;
}
.activeColor {
width: 60rpx;
height: 60rpx;
border-radius: 50%;
margin-left: 34rpx;
}
.colorContent {
height: calc(100% - 120rpx);
overflow-y: scroll;
}
{
"usingComponents": {
"color-picker":"../components/mini-color-picker/color-picker"
}
}
var app = getApp()
Page({
/**
* 页面的初始数据
*/
data: {
navBarHeight: app.globalData.navBarHeight,
navBarObj: {
bgColor: '#fff',
showBackBtn: true,
showHomeBtn: true,
title: '心理绘画'
},
appraisalPlanid: null, //患者疗愈计划id
id: null, //疗愈具体数据的id
x: 0, //开始的位置
y: 0,
newx: 0, //移动的位置
newy: 0,
timerValue: {
text: '00:00:00', //显示的时间
seconds: 0 //秒数
},
timer: null, //计时器
canvasType: 1, // 1 线 2 文本
inputValue: '', //文本内容
hiddenFormat: true,
focus: false,
showDialog: false, //底部的弹窗 1 颜色 2 文本格式
formatList: ['B', '/', 'U', 'S'],
textFormat: null, //选中的文本格式
brushList: [
{url: 'brushLine1.png', value: 1},
{url: 'brushLine2.png', value: 5},
{url: 'brushLine3.png', value: 10},
{url: 'brushLine4.png', value: 15},
{url: 'brushLine5.png', value: 20}
],
lineWidth: 1, //线的宽度
globalAlpha: '1', //笔的透明度 范围 0-1
brushDialogHidden: true,
activeColorList: [],
strokeStyle: 'rgb(0,0,0)',//线的颜色 初始值
pick: true
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
this.setData({
appraisalPlanid: options.appraisalplanid,
id: options.id
})
this._canvasInit();
this.getDrawingColor()
},
getDrawingColor() {
wx.request({
url: app.globalData.serverAddress + '/hcHealRemind/getUserDrawingColor',
method: 'GET',
data: {
userId: app.globalData.userid
},
success: (res) => {
if(res.data.code !== 0) {
return wx.showToast({
title: res.data.msg || '获取用户画板颜色数据失败',
icon: 'none'
})
}
this.setData({
activeColorList: res.data.data
})
}
})
},
_starTimer() {
let that = this;
if(that.data.timer) return
let setSeconds = `timerValue.seconds`
let textTime = `timerValue.text`
that.data.timer = setInterval(() => {
let seconds = that.data.timerValue.seconds + 1;
that.setData({
[setSeconds]: seconds,
[textTime]: that._getTimeBySecond(seconds)
})
}, 1000);
},
_endTimer() {
const that = this;
if(that.data.timer) {
clearInterval(that.data.timer)
}
},
// 秒转时间
_getTimeBySecond(seconds) {
let hour = Math.floor(seconds / 3600);
let minute = Math.floor(seconds % 3600 / 60);
let second = seconds % 3600 % 60
hour < 10 ? hour = '0' + hour : '';
minute < 10 ? minute = '0' + minute : '';
second < 10 ? second = '0' + second : '';
return hour + ":" + minute + ":" + second
},
_canvasInit() {
var ctx = wx.createCanvasContext('myCanvas');
ctx.setFontSize(16) //设置字体的字号
this.ctx = ctx
},
//触摸开始
start(e) {
this._starTimer(10);
let startx = e.changedTouches[0].x;
let starty = e.changedTouches[0].y;
this.setData({
x: startx,
y: starty
})
this.hideBrushDialog();
if(this.data.canvasType == 2) { //文本
this.setData({
hiddenFormat: false,
focus: true
})
}
},
//触摸移动
move(e) {
if(this.data.canvasType == 2) { //文本
return
}
let movex = e.changedTouches[0].x;
let movey = e.changedTouches[0].y;
this.setData({
newx: movex,
newy: movey
})
this._drawingLine(this.data.x, this.data.y, this.data.newx, this.data.newy)
this.setData({
x: movex,
y: movey
})
},
// 画线方法
_drawingLine(startx, starty, movex, movey) {
// 设置线
if(this.data.canvasType == 2) { //文本里面的线
this.ctx.setLineWidth(1)
this.ctx.strokeStyle = "rgb(0,0,0)"
this.ctx.setGlobalAlpha(1)
} else {
this.ctx.setLineWidth(this.data.lineWidth)
this.ctx.strokeStyle = this.data.strokeStyle
this.ctx.setGlobalAlpha(this.data.globalAlpha)
}
this.ctx.beginPath() //开始定义路径
this.ctx.moveTo(startx, starty) //起始点
this.ctx.lineTo(movex, movey) //连接到的坐标点
this.ctx.stroke() //沿着绘制的坐标点路径绘制直线
this.ctx.draw(true) //将之前在绘图上下文中画到 canvas 中
},
//绘制文本
_drawingText() {
let textFormat = this.data.textFormat;
this.ctx.font = "normal 16px Arial"
if(textFormat == 'B') {
this.ctx.font = "bold 16px Arial" //加粗
} else if(textFormat == '/') {
this.ctx.font = "italic 16px Arial" //倾斜
} else if(textFormat == 'U') { //下划线
let textWidth = this.ctx.measureText(this.data.inputValue).width;
this._drawingLine(this.data.x, this.data.y + 5, this.data.x + textWidth, this.data.y + 5)
} else if(textFormat == 'S') { //删除线
let textWidth = this.ctx.measureText(this.data.inputValue).width;
this._drawingLine(this.data.x, this.data.y - 5, this.data.x + textWidth, this.data.y - 5)
}
// this.ctx.setFillStyle('blue') //设置填充色
this.ctx.fillText(this.data.inputValue, this.data.x, this.data.y) //在画布上输出的文本 内容 x y
this.ctx.draw(true)
},
//点击文本
chengeText() {
this.setData({
canvasType: 2,
textFormat: null
})
},
//输入框
bindKeyInput(e) {
this.setData({
inputValue: e.detail.value
})
},
//输入框点击完成按钮
confirmInput(e) {
this._drawingText()
this.setData({
canvasType: 1,
hiddenFormat: true,
focus: false,
inputValue: ''
})
},
//点击格式
openFormat() {
this.setData({
showDialog: 2
})
},
//选中绘本文字的格式
changeFormat(e) {
this.setData({
textFormat: e.currentTarget.dataset.value
})
this.hideMask()
},
preventevents() {},
hideMask() {
if(this.data.showDialog == 1) {
this.addColor()
}
this.setData({
showDialog: false
})
if(this.data.canvasType == 2) {
this.setData({
focus: true
})
}
},
//打开画笔弹窗
openBrushDialog() {
this.setData({
brushDialogHidden: !this.data.brushDialogHidden,
hiddenFormat: true
})
},
//关闭画笔的弹窗
hideBrushDialog() {
if(!this.data.brushDialogHidden) {
this.setData({
brushDialogHidden: true
})
}
},
//点击底部区域
closeDialog() {
this.hideBrushDialog();
this.setData({
hiddenFormat: true
})
},
//选择画笔
checkBrush(e) {
this.setData({
lineWidth: e.currentTarget.dataset.value
})
},
// 画笔透明度
sliderchange(e) {
this.setData({
globalAlpha: e.detail.value
})
},
//打开颜色对话框
openColorDialog() {
this.setData({
showDialog: 1
})
},
//取色结果回调
pickColor(e) {
let rgb = e.detail.color;
this.setData({
strokeStyle: rgb
})
},
checkCorlor(e) {
this.setData({
strokeStyle: e.currentTarget.dataset.color,
showDialog: false
})
},
// 保存用户画板颜色
addColor() {
wx.request({
url: app.globalData.serverAddress + '/hcHealRemind/insertUserDrawingColor',
method: 'POST',
data: {
userId: app.globalData.userid,
drawingColor: this.data.strokeStyle
},
success: (res) => {
if(res.data.code !== 0) {
return wx.showToast({
title: res.data.msg || '保存用户画板颜色失败',
icon: 'none'
})
}
this.setData({
activeColorList: res.data.data
})
}
})
},
submit() {
let that = this;
that._endTimer();
wx.showLoading({
title: '图片生成中...',
})
wx.canvasToTempFilePath({
x: 0,
y: 0,
canvasId: 'myCanvas',
success(res) {
wx.uploadFile({
url: app.globalData.serverAddress + '/hcHealRemind/finishPsychologyDrawing',
filePath: res.tempFilePath,
name: 'file',
formData: {
appraisalPlanid: that.data.appraisalPlanid,
remindId: that.data.id,
workingHours: that.data.timerValue.seconds
},
success (res){
wx.switchTab({
url: '/pages/healing/index',
})
},
complete() {
wx.hideLoading()
}
})
}
})
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady() {
},
/**
* 生命周期函数--监听页面显示
*/
onShow() {
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide() {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload() {
this._endTimer();
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage() {
}
})