用H5中的Canvas等技術製作海報

在去年的時候也實現過合成海報的功能,不過當時時間倉促,實現的比較簡單。

就一個旋轉功能,圖片也不能拖動放大,也不能裁剪。

去年的實現可以參考《移動圖片操作--上傳》和《移動圖片操作--預覽旋轉合成

這次有時間就實現一個功能稍微多點的海報。

 

一、概要

第一屏

第二屏 第三屏

總共有三屏,第一屏是選擇圖片,第二屏是合成圖片,第三屏是顯示結果圖,可保存分享朋友圈。

頁面內容不是很多,分析起來也比較簡單。

1)每一屏的左右邊距相同,上邊距各不相同。

2)屏幕內的元素,大部分是居中,有些特殊邊距的可用絕對定位,例如第一屏中父親圖與標語圖,兩張圖有重疊部分。

3)第2和3屏中的按鈕佈局可以用Flex中的兩端對齊。

4)4種按鈕,可將背景製作成Sprite 圖,方便重用。1種彈出框,1種Loading。

5)有3種動畫,放大、脈衝以及旋轉360°。

6)這次實現的難點是拖動、裁剪和旋轉,需要經過邏輯計算高寬、座標等。

 

二、涉及的知識點

1)Sprite圖

移動端的Sprite圖在前面一篇《一張H5遊戲頁引起的思考》曾重點介紹過。

在移動端的話,位置就是用百分比來計算。從上面的總覽圖中可以看到多種按鈕背景,有幾個就是字不一樣,可以重複使用。

 

2)PrimusUI

PrimusUI是前面一段時間整理的一個微型UI庫,爲了提升開發效率,提取公用模塊而製作的。

有多個模塊可以使用,此次就用了三個模塊normalize、layout與loading。

具體內容可以參考前面一段時間寫的一篇介紹文《小身材大用途,用PrimusUI駕馭你的頁面》。

 

3)High DPI Canvas

引入High DPI Canvas,是爲了解決在高清屏的設備中,繪製在 canvas 中的圖形(包括文字)都會出現模糊的問題。

在demo代碼中有一張hidpi.html頁面,就是在比較引入此插件後表現的區別,下圖是在iphone6中展現的樣子。

可以看到原生的比較模糊,而引入了插件後就變的清晰了。原理就是讓Canvas中的1個像素等於屏幕中的1個物理像素,關於屏幕的概念可以參考《移動開發屏幕適配分析

下面是一段插件中的代碼,就是計算devicePixelRatio設備像素比)與webkitBackingStorePixelRatio(Canvas緩衝區的像素比),做個除法。

然後將Canvas的width和height根據這個比來放大,而CSS中的width和height再縮小回原來的,以此達到1像素的對應。

backingStore = context.backingStorePixelRatio ||
                    context.webkitBackingStorePixelRatio ||
                    context.mozBackingStorePixelRatio ||
                    context.msBackingStorePixelRatio ||
                    context.oBackingStorePixelRatio ||
                    context.backingStorePixelRatio || 1;

ratio = (window.devicePixelRatio || 1) / backingStore;

if (ratio > 1) {
    this.style.height = this.height + 'px';
    this.style.width = this.width + 'px';
    this.width *= ratio;
    this.height *= ratio;
}

 

4)touch.js

touch.js是個開源的手勢庫,第二屏中的拖動和捏(雙指放大縮小)就是通過這個庫實現的。

touch.on(touchPad, 'drag', function(ev) {
  //拖動邏輯
});
touch.on(touchPad, 'pinch', function(ev) {
  //捏的邏輯
});

 

5)FileReader

使用FileReader對象,web應用程序可以異步的讀取存儲在用戶計算機上的文件(或者原始數據緩衝)內容,可以使用File對象或者Blob對象來指定所要處理的文件或數據。

在執行上傳插件的“change”中,就是通過此對象獲取圖片的data:URL。

var file = $(this)[0].files[0];
var reader = new FileReader();
reader.readAsDataURL(file); // 將文件以Data URL形式進行讀入頁面
reader.onload = function() {
  var base64 = this.result;
};

 

三、實現

1)音頻控制

爲了營造父親節氣氛,特地選了首接地氣的歌曲配合頁面。

播放器就使用了HTML5標籤“audio”。

<audio id="audio" src="music.mp3" type="audio/mpeg"></audio>

這裏注意下,IOS是禁止自動播放音頻的,解決辦法就是不要自動播放,或者就是第一次點擊頁面觸發播放。

剩下的就是音頻標籤綁定播放和停止,在觸發的時候添加旋轉或脈衝動畫。

$audio.on("play", function() {
  isAudioLoaded = true;
  $music.addClass('music-rotate').removeClass('music-pulse');
}).on("pause", function() {
  $music.removeClass('music-rotate').addClass('music-pulse');
});

 

2)上傳圖片

上傳就是綁定file標籤的“change”事件,除了前面說到的用FileReader獲取圖片的data:URL外,還將原圖做了一次壓縮。

壓縮其實就是將圖片放到Canvas中,然後用Canvas輸出“jpeg圖片”,並且質量是“0.7”,可以將一張800多KB的png圖片壓縮到50多KB。

還發現一個現象,如果用Canvas輸出“png”的data:URL,會比原圖還要大。

   

在“reader.onload”事件中除了壓縮圖片,還會保存此圖的真實際寬度和高度,下面的旋轉會用到尺寸,還保存了一條旋轉信息的緩存。

var img = new Image();
img.onload = function() {
  var src = poster.filterImage(img, this.width, this.height); //將圖片進行壓縮,減少頁面大小
  $frameImg.data('width', this.width); //實際寬度
  $frameImg.data('height', this.height); //實際高度

  var realImg = new Image();
  realImg.onload = function() {
    $frameImg.attr('src', realImg.src); //第三次載入Base64數據
  };
  realImg.src = src;
  rotates[0] = {
    src: src,
    width: this.width,
    height: this.height,
    image: realImg
  }; //用於旋轉的緩存
};
img.src = base64;

 

3)拖拽、放大、縮小

此功能是需要與上面的touch.js手勢庫結合。

拖拽使用了CSS3的“translate3d”屬性,而放大縮小使用了CSS3的“scale”屬性。

function formatTransform(offx, offy, scale) {
  var translate = 'translate3d(' + (offx + 'px,') + (offy + 'px,') + '0)';
  scale = 'scale(' + scale + ')';
  //var rotate = 'rotate('+deg+'deg)';
  return translate + ' ' + scale;
}

原先旋轉也想用CSS3的“rotate”屬性實現,不過後面實現後,裁剪圖片變得非常棘手,不能下手,最後是否決了這個實現方式。

 

4)旋轉

爲了解決裁剪的問題,每次旋轉都會生成一張新的圖片,並將這個圖片信息緩存起來。

由於是新的圖片,所以就可以直接按照原先的方式來裁剪了,也不用考慮旋轉角度的問題。

旋轉的邏輯放在“filterImage”中,當時在編寫旋轉的時候,碰到旋轉後的圖形變形的問題,後面用圖片的實際寬高就解決了變形。

之所以變形是因爲寬高用了CSS計算後的值,下圖中的兩個尺寸就是計算後的值。

旋轉的代碼就兩行,rotate中“deg”就是旋轉角度,這裏是90。

ctx.rotate(deg * Math.PI / 180);
ctx.drawImage(image, 0, -canvas.width);

下圖介紹了操作過程:

爲了提升性能,每個方向的圖片信息都會被緩存起來。

rotates[direction] = {src:src, width:this.width, height:this.height, image:realImg};//緩存

 

5)裁剪

比較複雜的一部分,計算圖片相對於畫框的left和top邊距。

而right和bottom與以往的定義不同,這裏是高度與寬度分別和top與left相加後的值。

再根據不同邏輯,分別計算畫框與圖片的X、Y、width和height的值。

最後計算實際圖片的寬度與CSS計算後的圖片寬度比,將這個值與圖片的X、Y、width和height相乘,得出最終值。

這裏注意下,在iphone5S中,如果圖片的實際高度 < 計算後的高度,就會出現不顯示。具體的邏輯在“intersect”方法中。

下圖是某一種情況下的各個座標值:

intersect: function($frame, $img) {
  var imgX = 0,imgY = 0,imgW = 0,imgH = 0;
  var frmX = 0,frmY = 0;
  var imgOffset, frmOffset, left, right, top, bottom;

  imgOffset = $img.offset(); //圖片的偏移對象
  frmOffset = $frame.offset(); //畫框的偏移對象
  left = imgOffset.left - frmOffset.left - 3; //圖片到邊框左邊的距離 去除3px的邊框
  right = left + imgOffset.width; //畫框模型是border-box,所以圖片寬度需要減去邊框的寬度 就是574
  top = imgOffset.top - frmOffset.top - 3; //圖片到邊框上邊的距離
  bottom = top + imgOffset.height;

  //圖片在畫框內
  if (!(right <= 0 || left >= frmOffset.width || bottom <= 0 || top >= frmOffset.height)) {
    if (left < 0) {
      imgX = -left;
      frmX = 0;
      imgW = (right < frmOffset.width) ? right : frmOffset.width;
    } else {
      imgX = 0;
      frmX = left;
      imgW = (right < frmOffset.width ? right : frmOffset.width) - left;
    }

    if (top < 0) {
      imgY = -top;
      frmY = 0;
      imgH = (bottom < frmOffset.height) ? bottom : frmOffset.height;
    } else {
      imgY = 0;
      frmY = top;
      imgH = ((bottom < frmOffset.height) ? bottom : frmOffset.height) - top;

    }
  }

  var ratio = $img.data('width') / $img.width(); //圖片真實寬度 與 圖片CSS寬度
  //圖片的實際高度不能低於計算後的高度 否則iphone 5S中就不顯示
  var imageHeight = imgH * ratio;
  if (+$img.data('height') < imageHeight) {
    imageHeight = $img.data('height');
  }
  return {
    frame: {x: frmX,y: frmY,w: (imgW + 6),h: (imgH + 6)}, //此處畫框是574,而畫布是580
    image: {x: imgX * ratio,y: imgY * ratio,w: imgW * ratio,h: imageHeight}
  };
}

 

 

6)合成

合成其實就是將兩張Canvas合併到一起。下面代碼中的“drawImage”是自定義的一個方法,最終還是會調用Canvas的“drawImage”。

poster.drawImage(ctx, rotates[direction].image, poster.intersect($frame, $frameImg));
poster.drawImage(ctx, $word, poster.intersect($frame, $word));

Canvas的“drawImage”方法有多種參數組合。第三組有9個參數, 一開始還不是理解這幾個參數的含義,後面去查了一下。

sx、sy對應的是圖片的x、y座標,而dx、dy對應的是畫布的x、y座標。

 

 

 

demo下載:

https://github.com/pwstrick/father


轉自:http://www.cnblogs.com/strick/p/5593166.html

發佈了30 篇原創文章 · 獲贊 42 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章