微信小程序 画图 canvas 进阶版

微信小程序 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() {

  }
})

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值