最近要實現一個現場報名功能,採用B/S結構系統實現現場報名,現場採集用戶信息錄入系統。
前端使用bootstrap框架,後臺使用ASP.NET MVC4。
剛開始只需要用戶提供身份證,用讀卡器讀出身份證的信息自動填充到輸入框中,然後補充其他信息保存即可。
身份證讀卡器就是這貨,USB接口。
做了個操作界面:
讀身份證信息比較好辦,調用讀卡器接口對應的方法即可。
通過實驗發現接口方法讀取頭像照片時,得到的是Base64格式的jpg圖片,並且不包含“data:image/jpg;base64,”這段頭信息,需要自己手工加上。圖片尺寸爲102*126像素,在圖片上右擊看屬性:
提交報名信息時,後臺將獲取的Base64字符串轉換成jpg並保存在指定路徑下:
本來功能也就完成了,後來用戶提出沒帶身份證報名的情況,需要增加從文件選擇照片和現場用攝像頭拍照兩種方式,先感慨一下需求變更,還是要想辦法實現功能。
分析後決定將文件選取和攝像頭拍照用彈窗來操作,取得的圖片直接展示在頭像框處,並且爲了和身份證讀卡器讀出的圖片格式兼容,都設爲Base64格式。這樣就形成了統一思路,不論用什麼方式獲得頭像照片(讀身份證、文件選取、攝像頭拍照),均以Base64格式展示在操作界面,這樣提交到後臺處理時方式是一樣的。
================================= 分割一下 =================================
從文件中選取照片的處理
瀏覽照片選中時,用js控制在操作界面中展示選中照片,同時父窗口頭像框處展示經過Base64處理過的圖像。點擊右上角關閉按鈕或右下角確定按鈕時,關閉當前彈窗。
元素"imgFile"爲圖片選擇窗口中要展示的圖片,"imgAvatar"爲父窗口中的頭像框,"hPhoto"爲父窗口記錄Base64內容的隱藏域,注意去掉了"data:image/jpeg;base64,"這段頭信息,只保留圖片數據內容。
此時頭像框中圖片格式與讀身份證得到的圖片格式一樣,後臺保存方法都不用改。即使選擇的圖片格式不是jpg的,仍然會在後臺代碼中自動轉換成jpg格式。
=================================再分割一下=================================
攝像頭拍照的處理稍微麻煩一下,主要在於拍照的圖片是橫版的,但系統要求的頭像圖片是豎版的,需要進行裁切。
攝像頭就是普通那種USB接口的攝像頭,免驅動的。
網頁調用攝像頭拍照有多種方案,對比後採用的是flash方式。
項目中引用需要的資源文件:
頁面中調用:
<script src="/assets/js/jquery-1.8.3.min.js" type="text/javascript"></script>
<script src="/assets/js/webcam/jquery.webcam.js"></script>
思路是這樣的:flash顯示攝像頭實時畫面,點擊“拍照”按鈕時捕獲當前畫面,然後按比例裁切中間一部分圖像展示在畫布中。
攝像頭實時畫面設定爲320*240像素,按照頭像實際尺寸計算,應該截取拍照畫面中間的194*240區域。
爲了方便操作者預覽,在攝像頭實時畫面的左右兩側增加了一個半透明區域,意思是這兩塊區域要刪掉,只保留中間一部分。
彈窗中左側是攝像頭畫面,下方有“拍照”按鈕,拍照的圖片自動裁切後顯示在右側,父窗口同步顯示拍照結果。
左右兩側的半透明區域是用css控制的div透明度,注意flash調用攝像頭的z-index數值,要將半透明區域疊加到flash之上才行。
攝像頭畫面只保留中間的194*240區域的內容,因此拍照後要將原始圖片從(63,0)位置開始裁切大小爲194*240區域的圖像,js代碼是image = ctx.getImageData(63, 0, 194, 240);
顯示攝像頭畫面和抓拍畫面的html:
其中,id爲“webcam”的div在頁面加載時會append上flash,由flash調用攝像頭。
=================================待改進地方 =================================
1、程序中bootstrap框架採用ACE1.3.3版本,打開對話窗口時,是將彈窗文件解析後附加在本頁中,因此會受本月的腳本和代碼影響。從文件選取圖片功能用默認的對話窗口可以實現功能,但攝像頭拍照功能則不能生效,我只能用模態窗口來實現效果,並且在點擊“拍照”按鈕時將抓拍的圖片回傳給父窗口。
2、因客戶只使用IE瀏覽器,並且版本也固定,因此可以不用考慮過多兼容問題。身份證讀卡器使用Activex控件註冊,如果是火狐或谷歌瀏覽器要加載不同的控件。另外實時攝像頭畫面兩側半透明區域只在IE下起作用,其他瀏覽器要再處理。
=================================總結=================================
1、通過三種不同方式獲取頭像信息,傳到後臺保存時已經處理成同樣的格式,這也是代碼分離原則的體現。以頭像框爲中轉站,前臺js各種處理,達到同樣格式的Base64圖片;後臺獲取圖片字符串,統一保存成jpg格式。也相當於調用後臺的一個方法時,通過各種手段將傳入參數統一好格式,服務器端方法只需要接收到參數內容處理即可,不需要再做更多情況判斷。
2、不同版本的bootstrap有一定的瀏覽器兼容問題,特別是jquery版本。某些時候使用原生js是更好的選擇。
3、花哨的功能儘量少,用最簡單的方式實現功能,穩定性是最好的。
附:代碼文件
父頁面部分代碼
<input type="hidden" id="hPhoto" name="hPhoto" />
<input type="file" id="picFile" name="picFile" onchange="selectPicFile()" style="display:none;" />
<img id="imgAvatar" alt="" src="/assets/avatars/default.jpg" width="102" height="126" />
<div class="col-sm-4 align-left">
<a href="#" role="button" onclick="CapturePicture()"><i class="fa fa-camera fa-1x" title="從攝像頭拍照"> 拍照</i></a>
<br />
<a href="FilePicture" role="button" data-toggle="modal" data-target="#FilePicture"><i class="fa fa-folder-open fa-1x" title="從文件中選擇"> 文件</i></a>
</div>
<div id="FilePicture" class="modal fade" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div>
<script type="text/javascript">
jQuery(function ($) {
$("#FilePicture").on("hidden.bs.modal", function () {
$(this).removeData("bs.modal");
});
function CapturePicture() {
return window.showModalDialog("CapturePicture", GetResult, "dialogWidth=620px,dialogHeight=400px");
}
function GetResult(imageContent) {
document.getElementById('imgAvatar').src = imageContent;
document.getElementById('hPhoto').value = imageContent.substring(imageContent.indexOf(";base64,") + 8);
}
});
</script>
文件選擇子頁面(FilePicture)
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta charset="utf-8" />
<title></title>
<script type="text/javascript">
function showImage(file) {
var imageContent = '';
if (!file.files || !file.files[0]) {
return;
}
var reader = new FileReader();
reader.onload = function (evt) {
imageContent = evt.target.result;
document.getElementById('imgFile').src = imageContent;
document.getElementById('imgAvatar').src = imageContent;
document.getElementById('hPhoto').value = imageContent.substring(imageContent.indexOf(";base64,") + 8);
}
reader.readAsDataURL(file.files[0]);
}
</script>
</head>
<body class="no-skin">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3 class="smaller lighter blue no-margin">選擇照片</h3><h5>(尺寸:102*126像素或等比例)</h5>
</div>
<div class="modal-body">
<table width="100%" border="0" cellpadding="0" cellspacing="0">
<tr>
<td align="left">
<input type="file" id="picFile" name="picFile" onchange="showImage(this)" />
</td>
</tr>
<tr>
<td> </td>
</tr>
<tr>
<td>
<img id="imgFile" style="height:220px;" alt="" />
</td>
</tr>
</table>
</div>
<div class="modal-footer">
<button type="button" data-dismiss="modal" data-bb-handler="success" class="btn btn-sm btn-success">
<i class="ace-icon fa fa-check"></i> 確定
</button>
</div>
</body>
</html>
攝像頭抓拍子頁面(CapturePicture)
@{
Layout = null;
}
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>拍照</title>
<style type="text/css">
#webcam {
width: 320px;
}
#webcam {
position:relative;
}
object {
display:block; /* HTML5 fix */
position:relative;
z-index:100;
}
.div1{
width:320px;
height:240px;
position:relative;
border:1px solid #cccccc;
}
.div2{
width:63px;
height:240px;
position:absolute;
left:0;
top:0;
background:#000000;
z-index:9999;
opacity:0.3;
filter:alpha(opacity:30);
moz-opacity:0.3;
}
.div3{
width:63px;
height:240px;
position:absolute;
right:0;
top:0;
background:#000000;
z-index:9999;
opacity:0.3;
filter:alpha(opacity:30);
moz-opacity:0.3;
}
</style>
<script src="/assets/js/jquery-1.8.3.min.js" type="text/javascript"></script>
<script src="/assets/js/webcam/jquery.webcam.js"></script>
<script type="text/javascript">
var pos = 0;
var ctx = null;
var cam = null;
var saveCB;
var image = [];
$(document).ready(function () {
var canvas = document.getElementById("canvas");
if (canvas.getContext) {
ctx = document.getElementById("canvas").getContext("2d");
ctx.clearRect(0, 0, 320, 240);
var img = new Image();
img.src = "";
img.onload = function () {
ctx.drawImage(img, 0, 0);
}
image = ctx.getImageData(0, 0, 320, 240);
}
if (canvas.toDataURL) {
ctx = canvas.getContext("2d");
image = ctx.getImageData(63, 0, 194, 240);
saveCB = function (data) {
var col = data.split(";");
var img = image;
for (var i = 63; i < 257; i++) {
var tmp = parseInt(col[i]);
img.data[pos + 0] = (tmp >> 16) & 0xff;
img.data[pos + 1] = (tmp >> 8) & 0xff;
img.data[pos + 2] = tmp & 0xff;
img.data[pos + 3] = 0xff;
pos += 4;
}
if (pos >= 4 * 194 * 240) {
ctx.putImageData(img, 0, 0);
pos = 0;
}
};
} else {
saveCB = function (data) {
image.push(data);
pos += 4 * 194;
if (pos >= 4 * 194 * 240) {
pos = 0;
}
};
}
jQuery("#webcam").webcam({
mode: "callback",
swffile: "/assets/js/webcam/jscam_canvas_only.swf",
onCapture: function () {
webcam.save();
var canvas = document.getElementById("canvas")
var imageContent = canvas.toDataURL("image/png")
var callBack = window.dialogArguments;
if (callBack != undefined && callBack != null) {
callBack(imageContent);
}
},
onSave: saveCB,
debug: function (type, string) {
jQuery("#status").html(type + ": " + string);
},
onLoad: function () {
var cams = webcam.getCameraList();
for (var i in cams) {
jQuery("#cams").append("<li>" + cams[i] + "</li>");
}
}
});
});
</script>
</head>
<body class="no-skin" style="margin:10px;">
<table width="100%" border="0" cellpadding="0" cellspacing="0">
<tr>
<td height="56"><ul id="cams"></ul></td>
<td width="50"> </td>
<td><p id="status"></p></td>
</tr>
<tr style="height:3px;">
<td><hr /></td>
<td></td>
<td><hr /></td>
</tr>
<tr>
<td colspan="3"> </td>
</tr>
<tr>
<td align="center">
攝像頭畫面
</td>
<td></td>
<td align="center">
拍照結果
</td>
</tr>
<tr>
<td align="center">
<div id="webcam" class="div1">
<div class="div2"></div>
<div class="div3"></div>
</div>
</td>
<td></td>
<td align="center">
<div>
<canvas id="canvas" width="194" height="240"></canvas>
</div>
</td>
</tr>
<tr>
<td align="center"><a href="javascript:webcam.capture();void(0);">拍照</a></td>
<td></td>
<td></td>
</tr>
<tr>
<td colspan="3" height="20"> </td>
</tr>
<tr>
<td colspan="3" height="50" align="center" style="background-color:#cccccc"><input type="button" value="完成" onclick="window.close();" /></td>
</tr>
</table>
</body>
</html>