原文章地址: EOSONES博客
在本博客的用戶個人資料中允許用戶上傳頭像的過程中進行裁剪上傳,對於不熟悉前端的人來說有很多優秀的前端圖片裁剪插件可以使我們快速完成功能,本文使用的是功能全面的jQuery插件Cropper,結合本文進行簡單的應用介紹。查看全部參數設置推薦閱讀Cropper的基本使用。
安裝配置
1、下載地址
- 官方示例:官方示例
- jQuery:GitHub項目地址
- JS:GitHub項目地址
2、基本使用
首先要引入必要的js和css文件,上線可使用min壓縮版
<link href="/Myblog/static/css/cropper.css" rel="stylesheet">
<script src="/Myblog/static/js/cropper.js"></script>
可以將圖片或canvas直接包裹到一個塊級元素中
<!-- Wrap the image or canvas with a block element -->
<div class="container">
<img src="picture.jpg">
</div>
在JS中調用該圖片剪裁插件,即可在圖片上出現裁剪框
<script>
$('.container > img')').cropper({
aspectRatio: 1 / 1, #長寬比
viewMode:1, #視圖模式,可以使用0,1,2,3,具體查看demo示例
crop: function (e) {
// Output the result data for cropping image.
}
});
</script>
需要注意的是剪裁區域的尺寸繼承自圖片的父容器(包裹容器),所以要確保包裹圖片的是一個可見的塊級元素。輸出的剪裁數據基於原始的圖片尺寸,可以使用這些數據直接剪裁圖片。
應用項目
首先,根據需求,我們需要的是點擊頭像即可打開圖片,選中圖片后出現在模態框中的圖片即可進行裁剪,確認提交后通過Ajax傳給后端保存到服務器,將地址保存到用戶表的avatar字段。
1、隱藏上傳文件按鈕
/*首先畫個圓形*/
.circle {
width: 100px;
height: 100px;
border-radius: 50%;
-moz-border-radius: 50%;
-webkit-border-radius: 50%;
/*水平居中*/
margin: 25px auto 25px auto;
/*作用於子標簽*/
overflow: hidden;
position: relative;
text-align: center;
}
/*上傳文件樣式*/
.uploadhead {
position: absolute;
bottom: 0;
width: 100px;
height: 35px;
background-color: #000;
opacity: 0.7;
color: #fff;
font-size: 14px;
line-height: 30px;
display: none;
}
/*經過頭像顯示上傳文字*/
.circle:hover .uploadhead {
display: block;
color: #fff;
}
/*隱藏Input上傳按鈕*/
.uploadhead input {
position: absolute;
top: 0;
opacity: 0;
}
...
<div class="circle">
<img id="avatar" src="{{user.avatar.url}}">
<a href="javascript:" class="uploadhead">
<input type="file" name="file" onchange="preview(this)">上傳頭像
</a>
</div>
2、觸發事件
在這之前我們先引入需要用到的Bootstrap模態框,注意提前引入Bootstrap框架的相關文件。
<!-- 裁剪圖片Modal -->
<div class="modal fade" id="changeModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 class="modal-title" id="myModalLabel">請選擇合適的區域作為頭像</h4>
</div>
<div class="modal-body">
<div class="img-container">
<img id="uploadPreview" src="">
</div>
<div id="error_text" style="display:none"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
<button id='sendPhoto' type="button" class="btn btn-primary">上傳頭像</button>
</div>
</div>
</div>
</div>
注意我們在上傳頭像中的給Input標簽綁定的是onchange()事件,每次選擇圖片后才會觸發該事件,之后才開模態對話框。在preview函數中,首先我們判斷文件格式,之后打開model顯示錯誤信息,或者初始化cropper的相關設置,最后在model關閉的回調函數進行清空cropper及相關設置。
function preview(obj) {
var _alertMsg = document.getElementById('error_text');
var _myModalLabel = document.getElementById("myModalLabel");
//此事件在模態框被隱藏(並且同時在 CSS 過渡效果完成)之后被觸發。
$('#changeModal').modal('show').on('hidden.bs.modal', function(e) {
//關閉模態對話框后清空file input的值
$(obj).val('');
//隱藏錯誤提示
_alertMsg.style.display = 'none';
//清空圖片信息
document.getElementById("uploadPreview").src = '';
$("#uploadPreview").cropper('reset').cropper('replace', '');
//摧毀cropper
$("#uploadPreview").cropper("destroy");
//解綁changeModal的所有事件
$('#changeModal').off('shown.bs.modal');
$('#changeModal').off('hidden.bs.modal');
});
var val = obj.value;
//設定可上傳的格式
var upLoadType = '.jpg,.gif,.bmp,.png';
//從字符串中抽出最后一次出現.之后的字符,並且轉換成小寫
var fileExt = val.substr(val.lastIndexOf(".")).toLowerCase();
//查找后綴名是否符合條件,如果符合返回>=0,如果不符合則返回負數;
var result = upLoadType.indexOf(fileExt);
//如果只有一個文件則只需要訪問這個FileList對象中的第一個元素.
var oFile = obj.files[0];
//文件不存在直接返回或者不符合格式
if (obj.files.length === 0 || result < 0) {
_alertMsg.innerHTML = "請輸入正確格式:" + upLoadType;
_alertMsg.style.display = 'inline-block';
_myModalLabel.innerHTML = "上傳出現錯誤";
//隱藏圖片容器
document.getElementById("uploadPreview").parentElement.style.display = 'none';
return;
};
if (oFile.size / 1024 > 100) {
_alertMsg.innerHTML = "請上傳100k內的文件";
_alertMsg.style.display = 'inline-block';
_myModalLabel.innerHTML = "上傳出現錯誤";
//隱藏圖片容器
document.getElementById("uploadPreview").parentElement.style.display = 'none';
return;
};
//model顯示並在CSS過渡完成回調
$('#changeModal').on('shown.bs.modal', function() {
//轉為基於file API的Blob對象
var blobURL;
//URL對象是硬盤(SD卡等)指向文件的一個路徑
var URL = window.URL || window.webkitURL;
//獲得一個http格式的url路徑
blobURL = URL.createObjectURL(oFile);
document.getElementById("uploadPreview").parentElement.style.display = 'block';
document.getElementById("uploadPreview").src = blobURL;
//綁定cropper插件
$("#uploadPreview").cropper({
aspectRatio: 1, //1比1
viewMode: 3,
zoomOnWheel: false, //禁止縮放原圖
zoomOnTouch: false, //禁止縮放原圖
ready: function(data) {
// Output the result data for cropping image.
// And then
}
});
//重置cropper設置並替換生成的cropper圖片url
$("#uploadPreview").cropper('reset').cropper('replace', blobURL);
_myModalLabel.innerHTML = "請選擇合適的區域作為頭像";
});
};
最后,裁剪圖片后,通過model中的確認上傳按鈕中綁定的點擊事件用Ajax將數據發送后台。
$('#sendPhoto').on('click', function() {
var username = document.getElementById('username').innerHTML.trim();
// cropper可以得到兩種裁剪后圖片的數據(即blob和dataURL),dataURL過於長,此處用toBlob
var photo = $("#uploadPreview").cropper('getCroppedCanvas', {
width: 100,
height: 100,
}).toBlob(function(blob) {
//因為上傳的是文件不是string類型,因此用到H5的FormData方法
//組裝formdata
var fd = new FormData();
fd.append('username', username);
//fd.append("fileName", "avatar"); fileName為自定義,名字隨機生成或者寫死,看需求
fd.append("avatar", blob); //fileData為自定義,blob包含圖片的各種信息
fd.append("key","avatar");
//ajax上傳,ajax的形式隨意,JQ的寫法也沒有問題
//需要注意的是服務端需要設定,允許跨域請求。數據接收的方式和<input type="file"/> 上傳的文件沒有區別
$.ajax({
url: '/accounts/profile/update/',
type: 'post',
data: fd,
processData: false, //不設置Content-Type請求頭
contentType: false, //不處理發送的數據
success: function(data) {
var avaterurl = JSON.parse(data).url;
$("#avatar").attr("src", avaterurl);
$('#changeModal').modal('hide');
},error: function() { console.log("保存失敗"); }
});
});
});
3、視圖函數
首先配置更新用戶信息的url
#Myaccount/urls.py
from django.conf.urls import re_path
from . import views
app_name = "Myaccount"
urlpatterns = [
re_path(r'^profile/$', views.profile, name='profile'),
re_path(r'^profile/update/$', views.profile_update, name='profile_update'),
]
視圖函數處理用戶通過Ajax提交的個人網站與用戶頭像信息
#Myaccount/views.py
from django.shortcuts import render
from django.http import HttpResponse
from Myaccount import models
#auth中用戶權限有關的類。auth可以設置每個用戶的權限。
from django.contrib.auth.decorators import login_required
from django.views.decorators.csrf import csrf_exempt
# 使用login_required裝飾器,用戶只有登錄了才能訪問其用戶資料
@login_required
#個人信息
def profile(request):
# AUTH_USER_MODEL 類型的對象,表示當前登錄的用戶。
user = request.user
return render(request, 'account/profile.html', {'user': user})
import os
import json
import base64
from django.shortcuts import get_object_or_404
@csrf_exempt #取消當前函數防跨站請求偽造功能,即便settings中設置了全局中間件。
def profile_update(request):
#request.is_ajax(): #判斷請求頭中是否含有X-Requested-With的值
if request.is_ajax():
key=request.POST.get('key')#request.POST.get('')不存在默認為空,request.POST[]不存在報錯
username=request.POST['username']
user_profile=get_object_or_404(models.User,username=username)
if key=='link':
link=request.POST['link']
models.User.objects.filter(username=username).update(link=link)
link=models.User.objects.filter(username=username).first().link
linkJson={'link':link}
return HttpResponse(json.dumps(linkJson))
elif key=='avatar':
upload_image=request.FILES.get('avatar')
image_name=user_profile.save_avatar(upload_image)
user_profile.avatar=os.path.join('avatar',user_profile.username,image_name)
user_profile.save()
url=user_profile.avatar.url
dataJson={'url':url}
return HttpResponse(json.dumps(dataJson))
其中,由於未知原因部署后無法用Form、ModelForm等直接保存上傳的圖片文件,所以此處在User model中寫了一個手動儲存方法參考。
from django.db import models
#from django.contrib.auth.models import User
#AbstractUser類可自由定制需要的model
from django.contrib.auth.models import AbstractUser
#用pillow、django-imagekit模塊設置圖片,可以處理圖片,生成指定大小的縮略圖
from imagekit.models import ProcessedImageField
from imagekit.processors import ResizeToFill
#擴展Django自帶的User模型字
# 繼承 AbstractUser ,django 自帶用戶類,可擴展用戶個人信息,AbstractUser 模塊下有:password,username、first_name、last_name、email、last_login,is_superuser,is_staff,is_active,date_joined
class User(AbstractUser):
#nickname = models.CharField(max_length=30, blank=True, null=True, verbose_name='昵稱')
# 擴展用戶個人網站字段
link = models.URLField('個人網址', blank=True, help_text='提示:網址必須填寫以http開頭的完整形式')
# 擴展用戶頭像字段,upload_to后必須是相對路徑,上傳路徑已設置為media,保存的是圖片地址,前端user.avatar.url獲取
avatar = ProcessedImageField(upload_to='avatar',default='avatar/default.png',verbose_name='頭像',
processors=[ResizeToFill(100, 100)], # 處理后的圖像大小
format='JPEG', # 處理后的圖片格式
options={'quality': 95} # 處理后的圖片質量
)
#定義手動保存圖(IIS下User.save()保存失敗)
def save_avatar(self,upload_image):
import os
import uuid
from django.conf import settings
#創建與用戶名的文件夾
upload_path=os.path.join(settings.MEDIA_ROOT,'avatar',self.username)
if not upload_path:
try:
os.makedirs(new_path)
except:
pass
# 生成一個隨機字符串
uuid_str_name = uuid.uuid4().hex+'.jpg'
#保存
with open(os.path.join(upload_path,uuid_str_name), 'wb+') as file:
for chunk in upload_image.chunks():
file.write(chunk)
return uuid_str_name
#顯示用戶的郵箱是否驗證過,並提醒他們去驗證郵箱
def account_verified(self):
if self.user.is_authenticated: #django的auth系統功能,只能利用django自己的登陸方法才能判斷用戶是否登錄
result = EmailAddress.objects.filter(email=self.user.email)
if len(result):
return result[0].verified
return False
# 定義網站管理后台表名
class Meta:
verbose_name = '用戶信息'
verbose_name_plural = verbose_name #指定模型的復數形式是什么,如果不指定Django會自動在模型名稱后加一個’s’
ordering = ['-id']
#admin后台顯示名字關聯到此表的字段的后天顯示名字
def __str__(self):
return self.username
至此基本完成了用戶頭像的上傳交互,代碼中有許多不足望大佬指出,一起交流學習。
