功能需求
可以通過上傳兩個圖片乎完,一個是可以定制的T恤/背包等背景圖,一個是定制的logo圖片品洛。讓用戶可以可以拖動logo圖片放置在背景圖上粗略實現(xiàn)DIY的預覽效果树姨。具體要求:可手勢放大/縮小摩桶,可面板操作切換圖片,可面板操作放大縮小對應的圖片帽揪,可本地選擇圖片硝清。
實現(xiàn)效果
實現(xiàn)思路
原生容器組件的movable-area | 微信開放文檔 (qq.com)已經(jīng)內(nèi)部實現(xiàn)了拖動和放大縮小,我們只需要理順組件交互的思路以及注意事項转晰,主要有以下:
1.movable-view必須為movable-area的子級元素耍缴。
2.兩個movable-view不能同時設為可手勢放大/縮小,存在沖突挽霉,因此需要在點擊/拖動圖片,還有點擊下方tab切換背景圖/logo時控制相應的movable-view是否可手勢縮放变汪。
3.點擊或拖動logo/背景圖片時候侠坎,與下方的操作面板的tab元素互動,因此需要監(jiān)聽touchstart事件裙盾。
4.點擊/拖動logo時候实胸,需要顯示圖片邊框,在拖動結(jié)束的時候邊框消失番官,顯得更用戶友好庐完,因此需要在touchstart和touchend中做處理。
5.手勢放大/縮小時徘熔,需要同步下方操作面板的放大倍數(shù)门躯,因此需要綁定scale的值(movable-view提供)。
6.(重點)手勢放大縮小事件是一種resize事件酷师,如果每次resize都要更新一次面板計步器的話是十分浪費資源的讶凉,因此需要進行函數(shù)防抖(debounce),當觸發(fā)時山孔,如果規(guī)定時間間隔:500ms(個人設置的值)內(nèi)再次觸發(fā)resize事件懂讯,則把時間間隔更新,只有在最后一次resize事件執(zhí)行后且500ms內(nèi)沒有再次觸發(fā)resize事件台颠,才進行計步器值的更新褐望,具體防抖的原理和應用可以自行搜索。
代碼實現(xiàn)
WXML
<view class="diy-container">
<van-nav-bar
title="定制預覽"
left-text="返回"
left-arrow
class="head-nav-bar"
safe-area-inset-top="{{false}}"
bind:click-left="onClickLeft"
>
</van-nav-bar>
<view class="mv-container">
<movable-area class="mv-area" scale-area>
<movable-view model:scale="{{ chosenView === 'bg' }}" bindtouchstart="onBgTouchStart" bindscale="onBgScale" direction="all" model:scale-value="{{bgScaleRate}}" class="bg-view">
<view class="bg-view-label">
背景圖
</view>
<image mode="widthFix" class="bg-image" src="{{bgImagePath}}"/>
</movable-view>
<movable-view model:scale="{{ chosenView == 'logo' }}" bindtouchstart="onLogoTouchStart" bindtouchend="onLogoTouchingEnd" bindscale="onLogoScale" direction="all" scale-value="{{logoScaleRate}}" class="logo-view">
<view class="logo-view-label {{ isLogoTouching ? '' : 'logo-view-label-touching' }}">
logo
</view>
<image mode="widthFix" class="logo-image {{ isLogoTouching ? 'logo-image-touching' : ''}}" src="{{logoImagePath}}"/>
</movable-view>
</movable-area>
</view>
<view class="operation-container">
<van-tabs active="{{chosenView}}" bind:change="onTabChange" class="tabs" color="#409EFF">
<van-tab name="bg" class="bg-tab" title="背景圖">
<view wx:if="{{bgImagePath}}" class="bg-scale-rate-controller">
<view class="bg-scale-rate-label">
<view class="bg-scale-rate-text">
圖片縮放倍數(shù):
</view>
</view>
<view class="bg-scale-rate-stepper-container">
<van-stepper bind:change="onBgScaleRateChange" class="bg-scale-rate-stepper" model:value="{{ bgStepperValue }}" step="0.1" disable-input min="{{0.5}}" max="{{3}}" />
</view>
</view>
<view class="bg-selector-container">
<van-button bindtap="onBgPicChoose" size="small" type="primary" round>
本地選擇圖片
</van-button>
</view>
</van-tab>
<van-tab name="logo" title="logo">
<view wx:if="{{logoImagePath}}" class="logo-scale-rate-controller">
<view class="logo-scale-rate-label">
<view class="logo-scale-rate-text">
logo縮放倍數(shù):
</view>
</view>
<view class="logo-scale-rate-stepper-container">
<van-stepper bind:change="onLogoStepperValueChange" class="logo-scale-rate-stepper" value="{{ logoStepperValue }}" step="0.1" disable-input min="{{0.5}}" max="{{3}}" />
</view>
</view>
<view class="logo-selector-container">
<van-button bindtap="onLogoPicChoose" size="small" type="primary" round>
本地選擇圖片
</van-button>
</view>
</van-tab>
</van-tabs>
</view>
</view>
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({
/**
* 頁面的初始數(shù)據(jù)
*/
data: {
bgScaleRate: 1.0, //背景圖放大倍數(shù)
bgStepperValue: 1.0, // 背景圖放大倍數(shù)計步器數(shù)值
logoScaleRate: 1.0, // logo放大倍數(shù)
logoStepperValue:1.0, // logo計步器放大倍數(shù)
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是否顯示
},
/**
* 生命周期函數(shù)--監(jiān)聽頁面加載
*/
onLoad: function (options) {
},
/**
* 生命周期函數(shù)--監(jiān)聽頁面初次渲染完成
*/
onReady: function () {
},
/**
* 生命周期函數(shù)--監(jiān)聽頁面顯示
*/
onShow: function () {
},
/**
* 生命周期函數(shù)--監(jiān)聽頁面隱藏
*/
onHide: function () {
},
/**
* 生命周期函數(shù)--監(jiān)聽頁面卸載
*/
onUnload: function () {
},
/**
* 頁面相關事件處理函數(shù)--監(jiān)聽用戶下拉動作
*/
onPullDownRefresh: function () {
},
/**
* 頁面上拉觸底事件的處理函數(shù)
*/
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)
}
})
},
/**
* 背景圖片步進器值發(fā)生變化事件
*/
onBgScaleRateChange: function(value) {
this.setData({
bgScaleRate:value.detail
})
},
/**
* 背景圖片手勢縮放事件監(jiān)聽
*/
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觸摸結(jié)束事件
*/
onLogoTouchingEnd: function() {
this.setData({
isLogoTouching: false
});
},
/**
* logo圖片手勢縮放事件監(jiān)聽
*/
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防抖函數(shù)的實現(xiàn))
/**
* 防抖函數(shù)
* @param {*} fun 需要進行防抖的函數(shù)
*/
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); // 定時器為空表示可以執(zhí)行
timer = setTimeout(function() {
timer = null;// 到時間后設置定時器為空
},delay);
}
else {
// 如非立即執(zhí)行瘫里,則重設定時器
timer = setTimeout(function() {
fun.call(that,_args);
},delay);
}
}
}
json (代碼中用到的vant組件, 可以自行替換為原生組件)
{
"usingComponents": {
"van-tab": "@vant/weapp/tab/index",
"van-tabs": "@vant/weapp/tabs/index"
}
}
優(yōu)化
1.增加保存功能,對完成的圖片進行保存荡碾。
2.增加旋轉(zhuǎn)功能