微信小程式
對於微信小程式開發入門,還是比較簡單的,只需要具備基本的css+js知識就可以了,成本比較低。
寫了小程式和RN之後,有一種原生很笨重的感覺,就是小程式或者是RN等這些新的開發方式在效率上面真的有比較大的優勢,唯一不足就是執行速度了(使用Canvas就會有這樣子的感覺)。
感覺目前所接觸的種類前端開發(包括移動端),都是基本一個套路:UI,網路,資料儲存,富文字,圖片/視訊。
本文也是從這幾個方向去總結自己的小程式開發經驗。
小程式的入門
其實小程式的開發過程一直都是檢視文件,按照文件去操作就可以了。
一般流程是先看簡易教程。看完之後,再去看元件。之後可以開始嘗試寫需求,這個過程中,開始不斷的去查API和框架即可。
多列列表
在開發中,有一個需求是需要實現類似Android的GridView網格列表的。但是微信中並沒有提供這樣子的元件,但是小程式是跟html/css前端很類似的,他可以通過指定display:flex
,然後去設定flex-wrap:wrap
就可以。例如,有一個陣列data:["A","B","C","D","E","F","G","H","I","J","K","L","M","N"]
需要顯示為一個三列的列表,可以如下處理:
//GridPage.wxml
<view class=`grid-container`>
<view wx:for="{{data}}" wx:key="{{item}}" class="grid-list">
<view class=`grid-item`>
<text class=`grid-item-text`>{{item}}</text>
</view>
</view>
</view>
//GridPage.wxss
Page {
min-height: 100%;
background-color: #fff;
}
.grid-container {
margin-left: 4rpx;
margin-right: 4rpx;
display: flex;
flex-wrap: wrap;
flex-direction: row;
}
.grid-list {
width: 33.33%;
}
.grid-item {
margin: 2rpx;
background: #999;
display: flex;
justify-content: center;
align-items: center;
}
.grid-item-text {
color: black;
}
複製程式碼
這裡的重點就是grid-container
中的flex-wrap
為wrap
,方向是row
了。然後他的每一個item寬度都是33.33%
。需要注意的是一定是去設定外部的contanier而不是內部的list。
層級佈局
在CSS中,需要使用層級佈局,就是類似Android的FrameLayout效果,可以使用z-index,也可以使用一個絕對定位。比如,我們有一個需求是:下面是一個圖片,上面是文字。
//PositionPage.wxml
<view class=`root`>
<image src=`https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3756982450,995202616&fm=27&gp=0.jpg` class=`image`></image>
<text class=`text`>我是權律二啊</text>
</view>
//PositionPage.wcss
.root{
align-items: center;
display: flex;
flex-direction: column;
position: relative;
}
.image{
width: 300rpx;
height: 300rpx;
}
.text{
background-color: #999;
position: absolute;
}
複製程式碼
主要是兩點:父佈局的position
必須是relative
,它本身position
必須是absolute
。
網路請求
小程式的網路請求是使用wx.request()方法,但是該方法太臃腫,並沒有使用Promise那樣子簡潔。幸運的是小程式支援Promise,所以我們可以把http封裝一下,變成有條理。說到這裡,大家做的時候需要注意去微信後臺配置各種request域名,upload域名,downloadFIle域名。
下面封裝的例子的資料返回格式都是json格式post請求方式發出的:
//真正發起請求
function _request(url, param) {
if (isDebug) {
Log.i("http==> params->" + JSON.stringify(param));
Log.i("http==> url->" + url);
}
return new Promise((resolve, reject) => {
wx.request({
url: url,
data: param,
header: {
`content-type`: `application/json`,
"Accept": "application/json"
},
method: "POST",
success: function (response) {
if (isDebug) {
const jsonResponse = JSON.stringify(response);
Log.i("http==> response->" + jsonResponse);
}
const {data, statusCode, ok = false} = response;
//只有ok為true的是時候才返回成功,data不一定是包含資料的
if (statusCode === 200 && data && data.ok) {
resolve(data, ok);
} else {
if (statusCode != 404 && statusCode < 500 && statusCode > 300) {
ToastUtil.showError();
}
reject(data);
}
},
fail: reject
});
});
}
複製程式碼
使用:
function getInfo(fid) {
const params = {};
params.token = user.token;
params.uid = user.uid;
return _request("INFO_URL", params);
}
複製程式碼
然後需要發起請求就呼叫該方法即可,處理Promise。
上傳圖片到阿里雲
需要注意微信upload介面配置目前好像不可以直接配置阿里雲的URL,需要阿里雲先 跟我們的域名繫結,之後再去把設定到微信後臺的upload介面中。可以參考部落格:小程式圖片上傳阿里OSS使用方法,獲取簽名阿里雲Demo地址:JavaScript客戶端簽名直傳,通過打log獲得policy和signature(簽名時間可以稍微設定久一點)之後,就開始封裝upload方法了。
如下:
/**
* 真實上傳程式碼
*/
function _upload(file, success, fail) {
const suffix = file.substring(file.lastIndexOf("."));
//做一下md5處理
const fileName = hex_md5(file);
Log.i("fileName=" + (fileName + suffix));
wx.uploadFile({
url: ALIYUNPHOTOADDRESS,
formData: {
"OSSAccessKeyId": "你的阿里雲accessKey",
"key": DIR+ (fileName + suffix),
"policy": "你的policy",
"success_action_status": `200`,
"signature": "你的signature"
},
filePath: file,
name: `file`,
success: function (res) {
const {statusCode} = res;
if (statusCode === 200) {
console.log(JSON.stringify(res));
success("" + fileName + suffix);
} else {
console.log("上傳失敗");
fail(res);
}
},
fail: function (e) {
fail(e);
console.log("上傳失敗");
console.log("e=" + JSON.stringify(e));
}, complete: function () {
console.log("上傳過程結束");
}
})
}
}
複製程式碼
其中url
是上傳OOS的地址,key是需要上傳的資料夾+上傳之後的檔名。這裡的fileName我們通過一個md5去計算得來,保證唯一性又沒有什麼特殊字元。md5的演算法來自JS-MD5加密。
我們可以順帶封裝一個上傳多張圖片的方法,而且使用Promise返回:
/**
* files需要上傳的檔案,是一個陣列,裡面是檔案的絕對路徑
*/
function uploadFiles(files) {
if (!files || files.length <= 0) {
wx.showModal({
title: `圖片錯誤`,
content: `請重試`,
showCancel: false,
});
return Promise.reject();
}
Log.i("開始上傳" + files);
return new Promise((resolve, reject) => {
//上傳成功的檔名稱
let uploadPaths = [];
for (let i = 0; i < files.length; i++) {
_upload(files[i], (path) => {
//成功的檔名
uploadPaths[uploadPaths.length] = path;
if (uploadPaths.length >= files.length) {
//把url+name返回
resolve([ALIYUNPHOTOADDRESS + "/" +DIR, uploadPaths]);
}
}, () => {
//error
reject(res);
});
}
});
複製程式碼
Canvas使用
- 由於需要使用Canvas畫一棵樹,所以還是在這裡走了比較多的坑的。我的需求是Canvas全屏,除了畫一個樹之外還需要畫別的一些獨立Button。
首先,設定Canvas全屏和不可滑動,可以通過以下方式:
<canvas disable-scroll=`true` style="width: {{width}}px; height: {{height}}px;background-color:#efeff4;flex:1;" canvas-id="canvas" bindtouchstart="touchStart" bindtouchmove="touchMove" bindtouchend="touchEnd"></canvas>
複製程式碼
- 其中,在設定了disable-scroll設定為true,同時需要繫結三個touch事件,才能響應畫布的觸控event。其中這裡的width+height是通過wx.getSystemInfo()獲得。
- 其次,canvas沒有類似View的catchtap事件,只有一些touch事件,詳情可以看Canvas
- 然後在微信小程式中Canvas是層級最高的,無法通過設定z-index去調,所以假如你的Canvas全屏,還需要一些其他的Button,那麼只能通過最後canvas去draw了。
- 在canvas中,假如通過moveTo+lineTo去畫線,一般需要先呼叫canvas.beginPath()畫完成之後,先呼叫canvas.stoke(),然後在呼叫canvas.closePath();
c.beginPath();
c.setLineWidth(this.arrowPaint.width);
c.setStrokeStyle(this.arrowPaint.color);
c.setLineCap("square");
c.moveTo(this.arrowStartPointF.x, this.arrowStartPointF.y);
c.lineTo(this.arrowCenterPointF.x, this.arrowCenterPointF.y);
c.moveTo(this.arrowCenterPointF.x, this.arrowCenterPointF.y);
c.lineTo(this.arrowEndPointF.x, this.arrowEndPointF.y);
c.stroke();
c.closePath();
複製程式碼
- 在連續畫多種圖片/線條的時候,不要連續多次呼叫draw(true)方法, 消耗效能,一般最後呼叫fill()/stoke()方法即可。比如
//繪製點
c.beginPath();
let y = node.noteView.pointFrameCenter.y + Constant.FRAME_HEIGHT / 2 + Constant.GAP_BETWEEN_DOT + Constant.RADIUS_DOT;
c.setFillStyle(Constant.LINE_COLOR_RED);
c.setLineWidth(Constant.LINE_WIDTH);
c.arc(node.noteView.pointFrameCenter.x, y, Constant.RADIUS_DOT, 0, 2 * Math.PI);
//
y += (Constant.GAP_BETWEEN_DOT + Constant.RADIUS_DOT);
c.arc(node.noteView.pointFrameCenter.x, y, Constant.RADIUS_DOT, 0, 2 * Math.PI);
//
//
y += (Constant.GAP_BETWEEN_DOT + Constant.RADIUS_DOT);
c.arc(node.noteView.pointFrameCenter.x, y, Constant.RADIUS_DOT, 0, 2 * Math.PI);
c.fill();
c.closePath();
複製程式碼
- 對於draw方法,建議只是呼叫draw()就好,不要呼叫draw(true)方法,draw(true)是在原畫布之上再去畫,不會清空舊畫布,draw()會清空。一般,我們會在所有的image,rectangle,line,circle去fill/stoke完之後,再呼叫draw()方法,這樣子就可以避免draw(true)多次,效能耗損。而且再次去reDraw的時候也不用先去清空畫布。
- Canvas的跟隨手勢拖動
//index.wxml
<canvas disable-scroll=`true` style="width: {{width}}px; height: {{height}}px;background-color:#efeff4;" canvas-id="canvas" bindtouchstart="touchStart" bindtouchmove="touchMove" bindtouchend="touchEnd"></canvas>
//index.wcss
Page {
overflow: hidden;
display: flex;
}
//index.js
const app = getApp()
Page({
data: {
width: 0,
height: 0,
},
onLoad: function (e) {
this.time = (new Date()).valueOf();
this.x = 0;
this.y = 0;
this.moveX = 0;
this.moveY = 0;
const that = this;
wx.getSystemInfo({
success: function (res) {
that.setData({ width: res.screenWidth, height: res.screenHeight })
},
})
const ctx = wx.createCanvasContext("canvas", this)
this.canvas = ctx;
},
onReady: function () {
this.draw();
},
draw: function () {
this.canvas.fillRect(10, 10, 150, 100)
this.canvas.fill();
this.canvas.draw()
},
touchMove: function (e) {
console.log("touchMove")
let xOffset = e.touches[0].x - this.x;
let yOffset = e.touches[0].y - this.y;
this.x = e.touches[0].x;
this.y = e.touches[0].y;
this.moveX = this.moveX + xOffset;
this.moveY = this.moveY + yOffset;
this.canvas.translate(this.moveX, this.moveY);
this.draw();
},
touchStart: function (e) {
this.x = e.touches[0].x;
this.y = e.touches[0].y;
},
touchEnd: function (e) {
console.log("touchEnd")
}
})
複製程式碼
其他的scale等方法類似。