小程式—九宮格心形拼圖

FEWY發表於2018-07-31

說明

前幾天在朋友圈看到好幾次這種圖片。

圖片描述

這種圖片,是用九張圖片拼成的一個心形。

感覺很有趣,就上網查了查怎麼做,大部分的說法就是用美圖秀秀的拼圖功能來做, 在微信小程式中也有專門做心形拼圖的小程式,我都試了試之後,感覺還可以更加簡單一些,於是我就自己做了個小程式。

圖片描述

實現小程式的思路

1、有兩個 canvas,一個小的 canvas 顯示最後會是什麼樣子,一個大的 canvas 用來最後進行截圖,生成圖片,儲存到相簿。
通過CSS的定位,把大的 canvas 移到螢幕外面不讓使用者看到就可以了。
而如果用小的canvas 儲存圖片的話,最後的圖片有些模糊。

2、canvas 可以看成一個 9 * 9 的網格,

圖片描述

用一個叫 heart 的陣列來表示就是這樣的。

圖片描述

用其中的小格子,來拼出心形,根據陣列的內容在 canvas 上進行渲染。

小程式的功能

這個小程式有 選擇單張圖片,選擇多張圖片,補充圖片,儲存圖片,重置,推薦,意見反饋,這幾個功能。

選擇單張圖片

當使用者點選心形區域的時候,就可以選擇單張圖片,呼叫 wx.chooseImage 就可以從本地相簿選擇圖片,然後就把這張圖,畫在 canvas上,具體畫的位置就是使用者點選的位置。

在小的 canvas上繫結 touchend 事件,觸發事件後,事件中有一個 changedTouches 屬性,這是一個儲存了,當前變化的觸控點資訊的陣列,這個陣列中的元素有 x 和 y 屬性,也就是觸控點距離 canvas 左上角的距離。

// 觸控點在 x 軸的值
var x = e.changedTouches[0].x;
// 觸控點在 y 軸的值
var y = e.changedTouches[0].y;

有 x 軸 和 y 軸的距離後,算出具體應該畫在哪個格子上。

//grid 表示一個格子的寬度

// 確定 x 軸是在第幾個格子
x = Math.floor(x / grid);

// 確定 y 軸是在第幾個格子
y = Math.floor(y / grid);

知道在哪個格子畫之後,就要確定畫圖片的哪部分了,因為所有的格子都是正方形的,但是使用者選擇的圖片不一定是正方形,如果壓縮成正方形會很難看,所以我畫的時候,選擇了正中間的部分來畫,

通過 wx.getImageInfo 來獲取圖片資訊,以短邊為正方形的寬,然後從(長邊 - 短邊)/2 的地方來畫。

// 獲取圖片的寬和高
var width = res.width;
var height = res.height;

//  如果圖片不是正方形,只畫中間的部分
// sWidth 表示正方形的寬
var sWidth = width > height ? height : width;
// sx 是源影像的矩形選擇框的左上角 X 座標
var sx = 0;
// sy 是源影像的矩形選擇框的左上角 y 座標
var sy = 0;
if (width > height) {
    sx = (width - height) / 2;
}
if (width < height) {
    sy = (height - width) / 2;
}

知道畫什麼,在哪裡畫之後,呼叫 canvasContext.drawImage 來畫就可以了。

選擇多張圖片

選擇多張圖片,同樣是呼叫 wx.chooseImage 方法,成功選擇多張圖片後,返回的物件中有一個 tempFilePaths 屬性,這個屬性儲存了,圖片的本地檔案路徑列表。

圖片描述

然後遍歷 heart 陣列,也就是儲存心形資料的陣列,如果陣列中某個元素的值是1,也就是說在心形範圍內,就按順序從 tempFilePaths 中取一張圖片畫上去,畫的時候同樣的,如果不是正方形就只畫中間的部分。

補充圖片

在 image 的檔案中,有儲存幾張圖片,用來補充心形,他們的路徑儲存在一個陣列中。

 // 用來補充心形的圖片
 images: [
   `../../images/1.jpg`,
   `../../images/2.jpg`,
   `../../images/3.jpg`,
   `../../images/4.jpg`,
   `../../images/5.jpg`,
   `../../images/6.jpg`,
   `../../images/7.jpg`,
   `../../images/8.jpg`,
   `../../images/9.jpg`,
   `../../images/10.jpg`,
 ]

然後就是遍歷 heart 陣列,如果陣列的某個元素的值是1,就隨機從這組圖片中選擇一張畫上去。

畫一張圖片,畫多張圖片,補充圖片,他們都是在 canvas 上畫圖片,為了避免已經畫了圖片的位置被覆蓋,他們所畫的圖片的等級是不同的。

補充圖片:1
畫多張圖片:2
畫一張圖片:3

等級高的可以覆蓋等級低的,等級低的不能覆蓋等級高的,同等級的,除了畫多張圖片的不能覆蓋,其餘的兩種情況,都可以覆蓋。

簡單意思就是:
補充圖片,補充完了之後,再補充會把原來補充的覆蓋掉,但是使用者選擇的圖片不會被覆蓋掉。
畫多張圖片,可以覆蓋掉補充的圖片,但使用者選擇的圖片也不會覆蓋掉。
畫一張圖片,不管這個位置有沒有圖片,都會再畫一張。

儲存圖片

儲存圖片的時候,就是按順序對大的 canvas 進行擷取,然後儲存成圖片,主要靠 wx.canvasToTempFilePath 這個API來實現,這個 API ,可以把當前畫布指定區域的內容匯出生成指定大小的圖片,並返回檔案路徑。

這裡要注意幾個細節

1、為了避免最後儲存的圖片有黑色背景,最好開始的時候就在 canvas 上畫一個 和 canvas 大小一樣的矩形,矩形填充上顏色。

2、為了儲存的圖片,在使用者的相簿中也能保持心形。需要按下面這個順序來儲存圖片

圖片描述

3、wx.canvasToTempFilePath 中有兩個選填的引數 destWidth 和 destHeight,這個兩個引數決定 輸出圖片寬度和高度,如果不是準確的知道是多少,用預設值就可以。

destWidthdestHeight 單位是物理畫素(pixel),canvas 繪製的時候用的是邏輯畫素(物理畫素=邏輯畫素 * density),所以這裡如果只是使用 canvas 中的 width 和 height(邏輯畫素)作為輸出圖片的長寬的話,生成的圖片 width 和 height 實際上是縮放了到 canvas 的 1 / density 大小了,所以就顯得比較模糊了。

而預設值是 width * 螢幕畫素密度

圖片描述

文件中提到的螢幕畫素密度,應該不是指每英寸螢幕所擁有的畫素數,而是指裝置畫素比(pixelRatio),也就是用多少個物理畫素去顯示 1px 的 CSS 畫素。
用API wx.getSystemInfo 可以檢視裝置畫素比

wx.getSystemInfo({
  success: function(res) {
    console.log(res.pixelRatio)
  }
})

這裡如果我的理解有誤,還請知道的小夥伴指出。

說了這麼多,主要就是想說用預設的值其實就已經很清晰了。

4、因為要儲存9張圖片,所以需要一些時間,這個時候就需要一個進度條了,儲存圖片的時候,顯示進度條,禁用儲存按鈕,畢竟點選一下按鈕就是9張圖片,所以這個時候還是禁用了好,每儲存一張圖片進度條的值就 +12 ,超過100的時候,就表示 9張圖片都儲存好了。

而微信小程式中也剛好有進度條(progress)這個元件

重置

這個功能就是遍歷 heart 陣列,用一種顏色,根據陣列內容,把心形畫出來。然後再在 x 軸 和 y 軸上畫兩條線,行成九宮格的樣子。

推薦 和 意見反饋

 <button open-type=`share`>推薦給朋友</button>
 <button open-type=`feedback`>意見反饋

這個兩個功能就是用了,微信小程式的 button 元件,這裡需要注意的就是,在清除 button 的預設樣式時,需要把 button 的 after 偽元素的邊框也去掉。

button::after{
  border: 0; 
}

可以優化的地方

有一些地方是小程式在替使用者做選擇,比如,如果所選擇的圖片不是正方形,就畫中間的部分,但是中間的部分不一定是使用者想要的,而如果每張圖片都要使用者自己來選擇畫哪部分,顯然是有些麻煩了,這裡還可以繼續優化下。

還有在補充圖片的時候,補充的圖片也不一定是使用者喜歡的,所以這部分再考慮是不是可以加一些標籤,使用者選擇不同的標籤,來補充符合標籤的圖片,類似 QQ音樂的歌詞海報這樣。

圖片描述

總結

這次做的這個九宮格心形拼圖的小程式,第一版已經上線了。

圖片描述

開源地址:https://github.com/FEWY/jigsaw
如果你喜歡這個小程式的話,可以 star 支援一下。

這個小程式不管在程式碼,還是功能上都還有許多地方可以繼續優化,如果有需要的朋友可以直接拿去改。

前端簡單說

相關文章