標籤:

上傳頭像

簡單需求

  1. 點擊頭像可以選擇圖片
  2. 確定選擇後頁面顯示圖片名字,並出現上傳按鈕
  3. 點擊上傳按鈕出現裁剪頁面,可以對上傳的圖片進行裁切
  4. 確定後修改原來頭像

依賴第三方

  • NodeJS:作為後台語言使用
  • Express:簡單伺服器搭建、路由處理
  • EJS:伺服器端渲染頁面時使用的模板引擎
  • Jcrop:前端處理圖像裁剪時用到的庫,基於jQuery
  • GM:後端處理圖像裁剪時用的包
  • Formidable:上傳圖片時用到的包
  • Supervisor:監聽Node代碼修改,並自動重啟,使實時生效

新建文件夾

public:用來放置一些靜態資源

router:路由配置相關的代碼

upload:裝上傳圖片的文件夾

views:視圖文件,也是EJS模板引擎默認的文件夾

app.js:程序的入口


初始化package.json文件並安裝需要使用的包

初始化package.json

安裝依賴包


展示首頁

編寫app.js,輸入命令supervisor app啟動伺服器(要先先全局安裝supervisor),輸入localhost:4000時使顯示 views 目錄下的index.html頁面。

let express = require("express"),n app = express(),n path = require("path");nn// 設置模板引擎,並將模板的後綴改為.htmlnapp.set("views", path.join(__dirname, views));napp.engine(".html", require("ejs").__express);napp.set("view engine", "html");nn// 使用中間件,指定靜態資源跟目錄是publicnapp.use(express.static("public"));n// 指定上傳圖片根目錄,方便後面使用,例如可以直接這樣寫src="/upload/xxx.png"napp.use("/upload",express.static("upload"));nn// 首頁napp.get("/", function(req, res) {n // 渲染頁面n res.render("index");// index指的是index.htmln});nnlet server = app.listen(4000, function () {n console.log("running localhost:4000...");n});n


編寫首頁前端頁面(index.html)

index.html

<!-- enctype的寫法是配合formidable完成圖片上傳,後面會用到 -->n<form action="/upload" enctype="multipart/form-data" method="post">n <div class="avatar">n <img src="/upload/avatar.jpg" alt="">n <!-- 注意頭像上面覆蓋的有一個可以點擊input上傳框 -->n <!-- 注意最好不要這樣寫accept="image/*",我的chrome58.0.3029.110會出現彈出框延遲好久才出現 -->n <input type="file" id="fileInput" name="upload" accept="image/jpg,image/jpeg,image/png">n </div>n <div id="uploadInput" class="upload-input">avatar.jpg</div>n <input type="submit" class="upload-btn" id="uploadBtn">n</form>n

不點擊頭像直接點擊提交按鈕會上傳失敗,除了對後端返回錯誤信息進行相應處理外,可以換一種思路解決此問題:開始先把上傳按鈕隱藏起來,當判斷上傳有內容時再顯示,這樣也就保證了點擊提交按鈕時是有進行文件選擇的,不失為一種好辦法~

$("#fileInput").on("change", function () {n let filePath = $(this).val();n // 當上傳文件名不為空時顯示上傳按鈕n if (filePath !== "") {n $("#uploadBtn").show();n }n});n


處理默認頭像

此時的默認頭像avatar.jpg是我們前端寫死的,這是不應該的,我想要在訪問localhost:4000時是後端給我默認圖片的信息,此時router文件夾下新建router.js用來編寫後端路由相關代碼。實現上面的需求我們有以下3個文件作出了變動。

app.js

let router = require("./router/router");napp.get("/", router.index);n

router/router.js

let imgName = "avatar.jpg";nnexports.index = function(req, res) {n res.render("index", {n imgNamen });n};n

views/index.html

<!-- 頭像地址 -->n<img src="/upload/<%=imgName%>" alt="">n<!-- 頭像名字 -->n<div id="uploadInput" class="upload-input"><%=imgName%></div>n

此時刷新localhost:4000,雖頁面並無變化,但我們的默認頭像其實是後端輸出的了,而非寫死的,簡直棒棒噠。


編寫圖像上傳介面(upload)

exports.upload = function (req, res) {n if (req.url == "/upload" && req.method.toLowerCase() == "post") {n let form = new formidable.IncomingForm();n // 指定上傳路徑n form.uploadDir = path.join(__dirname, /../upload);nn form.parse(req, function (err, fields, files) {n res.send({n ok: true,n msg: "上傳成功"n });n res.end();n });n }n};n

選擇圖像,點擊上傳按鈕後會發現upload目錄多了類似upload_1b431bebaad5ab4d96019987f9531db8的文件,這個就是我們上傳成功的文件,在末尾加上相應圖片後綴就能打開圖片啦(使用fs.rename改名時順帶加上相應後綴名),在後端利用gm對圖片進行縮放和裁剪(目的是使符合前端對裁切圖片的需要,不要直接在前端對上傳的圖片進行縮放,這樣不容易找出截取區域的坐標),最後把這樣處理過的成品圖片給到views/cutimage.html頁面。

exports.upload = function (req, res) {n if (req.url == "/upload" && req.method.toLowerCase() == "post") {n let form = new formidable.IncomingForm();n // 指定上傳路徑n form.uploadDir = path.join(__dirname, /../upload);nn form.parse(req, function (err, fields, files) {n let extName = path.extname(files.upload.name),n oldFile = files.upload.path,n newFile = path.join(__dirname, /../upload/) + Math.random() + extName;nn imgName = path.basename(newFile);nn // 到這裡說明上傳成功了n fs.rename(oldFile, newFile, function (err) {n if (err) {n res.send({n ok: false,n msg: "上傳成功但改名失敗"n });n return;n }n // 對上傳成功且改名成功後的圖片進行縮放裁剪後渲染cutimage頁面並返回圖片地址n gm(newFile)n .resize(580)// 等比變成寬度580n .crop(580, 580, 0, 0)// 保留高度580,使不溢出n .write(newFile, function (err) {n if (err) {n res.send({n ok: false,n msg: "寫入失敗"n });n return;n }n res.render("cutimage", {n imgNamen });n });n });n });n }n};n


編寫前端裁剪頁面(cutimage.html)

cutimage.html

對應JS代碼如下

let imgname = "<%=imgName %>";n$("#cutImage").on("click", function () {n let w = parseInt($(".jcrop-holder>div:first").css("width")),n h = parseInt($(".jcrop-holder>div:first").css("height")),n x = parseInt($(".jcrop-holder>div:first").css("left")),n y = parseInt($(".jcrop-holder>div:first").css("top"));n // 真正裁剪介面n $.get("/handleCut", {n imgname,n w,n h,n x,n yn }, function(data) {n if(data.ok) {n // 裁剪成功調到首頁n window.location = "/";n }n });n});n


編寫後端裁剪介面(handleCut)

exports.handleCut = function(req, res) {n let imgName = req.query.imgname,n w = req.query.w,n h = req.query.h,n x = req.query.x,n y = req.query.y,n newFile = path.join(__dirname, /../upload/) + imgName;n gm(newFile)n .crop(w, h, x, y)n .resize(100, 100, "!")// "!"表示強制縮放成目標大小n .write(newFile, function(err) {n if(err) {n res.send({n ok: false,n msg: "寫入失敗"n });n return;n }n res.send({n ok: true,n msg: "成功"n });n });n};n

查看源代碼

題圖來源:Kristopher Roller


推薦閱讀:

[live預告] 聊聊前端培訓那些事
一入前端深似海,從此紅塵是路人系列第六彈之走進數據可視化

TAG:前端开发 |