微信小程序基于movable-area实现DIY T恤/logo定制

功能需求

可以通过上传两个图片,一个是可以定制的T恤/背包等背景图,一个是定制的logo图片。让用户可以可以拖动logo图片放置在背景图上粗略实现DIY的预览效果。具体要求:可手势放大/缩小,可面板操作切换图片,可面板操作放大缩小对应的图片,可本地选择图片。

实现效果

实现效果.png

实现思路

原生容器组件的movable-area | 微信开放文档 (qq.com)已经内部实现了拖动和放大缩小,我们只需要理顺组件交互的思路以及注意事项,主要有以下:
1.movable-view必须为movable-area的子级元素。

2.两个movable-view不能同时设为可手势放大/缩小,存在冲突,因此需要在点击/拖动图片,还有点击下方tab切换背景图/logo时控制相应的movable-view是否可手势缩放。

3.点击或拖动logo/背景图片时候,与下方的操作面板的tab元素互动,因此需要监听touchstart事件。

4.点击/拖动logo时候,需要显示图片边框,在拖动结束的时候边框消失,显得更用户友好,因此需要在touchstart和touchend中做处理。

5.手势放大/缩小时,需要同步下方操作面板的放大倍数,因此需要绑定scale的值(movable-view提供)。

6.(重点)手势放大缩小事件是一种resize事件,如果每次resize都要更新一次面板计步器的话是十分浪费资源的,因此需要进行函数防抖(debounce),当触发时,如果规定时间间隔:500ms(个人设置的值)内再次触发resize事件,则把时间间隔更新,只有在最后一次resize事件执行后且500ms内没有再次触发resize事件,才进行计步器值的更新,具体防抖的原理和应用可以自行搜索。

代码实现

WXML


  
  
  
    
      
        
          背景图
        
        
      
      
        
          logo
        
        
      
    
  
  
    
      
        
          
            
              图片缩放倍数:
            
          
          
            
          
        
        
          
          本地选择图片
          
        
      
      
        
          
            
              logo缩放倍数:
            
          
          
            
          
        
        
          
            本地选择图片
          
        
      
    
  

WXSS

page {
  padding: 0;
  margin: 0;
}
.diy-container {
  width: 100%;
  height: 100vh;
  display: flex;
  flex-direction: column;
}
.head-nav-bar {
  padding: 0px;
  margin: 0;
}
.mv-container {
  flex-grow: 1;
}
.mv-area {
  background: greenyellow;
  left: 2.5%;
  width: 95%;
  height: 100%;
}
.bg-view {
  width: 90%;
  height: 80%;
  top: 10%;
  left: 5%;
  position:  relative;
}
.bg-view-label {
  background: blue;
  color: white;
  display: inline-block;
  padding: 5px;
  font-size: 20rpx;
}
.bg-image {
  width: 100%;
}
.logo-view {
  width: 20%;
  left: 40%;
  top: 20%;
}
.logo-view-label {
  color: white;
  display: inline-block;
  padding: 5px;
  font-size: 20rpx;
  background: red;
}
.logo-view-label-touching {
  opacity: 0;
  transition: .3s opacity ease-in-out;
}
.logo-image {
  width: 100%;
  border: 1px solid transparent;
  transition: .3s border ease-in-out;
}
.logo-image-touching {
  border: 1px dashed red;
  transition: .3s border ease-in-out;
}
.operation-container {
  height: 20vh;
  min-height: 100px;
  position: relative;
  background: #fff;
}

.bg-scale-rate-controller {
  display: flex;
  align-items: center;
  padding-left: 30rpx;
  margin-top: 15rpx;
}
.bg-scale-rate-label {
  flex-grow: 1;
  text-align: left;
}
.bg-scale-rate-stepper-container {
  flex-grow: 1;
}
.bg-selector-container {
  margin-left: 30rpx;
  margin-top: calc(20vh - 74px - 40px - 15rpx);
}

.logo-scale-rate-controller {
  display: flex;
  align-items: center;
  padding-left: 30rpx;
  margin-top: 15rpx;
}
.logo-scale-rate-label {
  flex-grow: 1;
  text-align: left;
}
.logo-scale-rate-stepper-container {
  flex-grow: 1;
}
.logo-selector-container {
  margin-left: 30rpx;
  margin-top: calc(20vh - 74px - 40px - 15rpx);
}

js

import { debounce } from '../../utils/utils'
Page({

  /**
   * 页面的初始数据
   */
  data: {
    bgScaleRate: 1.0, //背景图放大倍数
    bgStepperValue: 1.0, // 背景图放大倍数计步器数值
    logoScaleRate: 1.0, // logo放大倍数
    logoStepperValue:1.0, // logo计步器放大倍数
    bgImagePath:'https://img.zcool.cn/community/01310c5afd1b97a801218cf453e8a4.jpg@1280w_1l_2o_100sh.jpg', // 背景图路径
    logoImagePath:'https://www.logosc.cn/uploads/icon/2018/10/10/dfd25b38-ef01-4d83-abdb-57d1e0bfc25a.png', // logo图路径
    chosenView:'bg',  // 当前选择movable-view, 用于该元素是否可以手势放大
    isLogoTouching: true  // 是否正在点击/拖动logo,用于控制logo的边框线和label是否显示
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {

  },

  /**
   * 生命周期函数--监听页面初次渲染完成
   */
  onReady: function () {

  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function () {

  },

  /**
   * 生命周期函数--监听页面隐藏
   */
  onHide: function () {

  },

  /**
   * 生命周期函数--监听页面卸载
   */
  onUnload: function () {

  },

  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh: function () {

  },

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom: function () {

  },

  /**
   * 用户点击右上角分享
   */
  onShareAppMessage: function () {

  },
  /**
   * 背景图片选择
   */
  onBgPicChoose: function() {
    const that = this;
    wx.chooseMedia({
      count:1,
      mediaType:['image'],
      sourceType:['album'],
      success(res) {
        if(res.tempFiles[0]?.tempFilePath) {
          that.setData({
            bgImagePath: res.tempFiles[0].tempFilePath,
            bgScaleRate: 1,
            bgStepperValue: 1
          });
        }
      }
    })
  },
  
  /**
   * Logo选择
   */
  onLogoPicChoose: function() {
    const that = this;
    wx.chooseMedia({
      count:1,
      mediaType:['image'],
      sourceType:['album'],
      success(res) {
        that.setData({
          logoImagePath: res.tempFiles[0].tempFilePath,
          isLogoTouching: true,
          logoStepperValue: 1,
          logoScaleRate: 1
        });
        // console.log(res.tempFiles.size)
      }
    })
  },

  /**
   * 背景图片步进器值发生变化事件
   */
  onBgScaleRateChange: function(value) {
    this.setData({
      bgScaleRate:value.detail
    })
  },
  /**
   * 背景图片手势缩放事件监听
   */
  onBgScale: debounce(function(event) {
    if(event.detail.scale != this.data.bgScaleRate) {
      this.setData({
        bgStepperValue: event.detail.scale      
      });
    }
  }),
  /**
   * 背景图触摸开始事件
   */
  onBgTouchStart: function() {
    this.setData({
      chosenView:'bg'
    })
  },
  
  /**
   * logo缩放计步器值改变事件
   */
  onLogoStepperValueChange: function(event) {
    this.setData({
      logoScaleRate: event.detail
    });
  },

  /**
   * logo触摸开始事件
   */
  onLogoTouchStart: function() {
    this.setData({
      isLogoTouching: true,
      chosenView:'logo'
    });
  },

  /**
   * logo触摸结束事件
   */
  onLogoTouchingEnd: function() {
    this.setData({
      isLogoTouching: false
    });
  },

  /**
   * logo图片手势缩放事件监听
   */
  onLogoScale: debounce(function(event) {
    if(this.data.logoScaleRate != event.detail.scale) {
      this.setData({
        logoStepperValue: event.detail.scale
      });
    }
  }),

  /**
   * 选项卡点击事件
   */
  onTabChange: function(event) {
    this.setData({
      chosenView: event.detail.name
    })
  },
  /**
   * 顶部返回点击事件
   */
  onClickLeft: function() {
    let pageObject = getCurrentPages();
    if(pageObject.length == 1) {
      wx.navigateTo({
        url: '/pages/index/index',
      })
    }
  }
})

utils(debounc防抖函数的实现)

/**
 * 防抖函数
 * @param {*} fun 需要进行防抖的函数 
 */
export function debounce(fun, delay = 500, immediate= false) {
  let timer = null; // 保存定时器
  return function(args) {
    let that = this;
    let _args = args;
    if(timer) clearTimeout(timer);
    if(immediate) {
      if(!timer) fun.apply(that,_args); // 定时器为空表示可以执行
      timer = setTimeout(function() {
        timer = null;// 到时间后设置定时器为空
      },delay);
    }
    else {
      // 如非立即执行,则重设定时器
      timer = setTimeout(function() {
        fun.call(that,_args);
      },delay);
    }
  }
}

json (代码中用到的vant组件, 可以自行替换为原生组件)

{
  "usingComponents": {
    "van-tab": "@vant/weapp/tab/index",
    "van-tabs": "@vant/weapp/tabs/index"
  }
}

优化

1.增加保存功能,对完成的图片进行保存。
2.增加旋转功能

你可能感兴趣的:(微信小程序基于movable-area实现DIY T恤/logo定制)