很早就想分享給大家了。由于比較忙且懶-,
之前開發有這樣一個需求,像微信朋友圈一樣發布動態的一個功能。剛開始想嘗試百度下有沒有合適的插件。百度了很久都沒找到合適的,大都是可以上傳不能預覽。或者是預覽功能有問題。無奈之下,還是靠自己吧。
其實想一想還是挺簡單,上傳就不用說了通過input的file獲取到文件再通過fileReader將圖片路徑轉成Base64。比較大的難題是點擊圖片預覽,且可以滑動。
- 基本思路就是先寫好基本樣式(遮罩啊等等)然后通過當前圖片點擊獲取到圖片路徑的集合,拿到集合后寫個輪播,并且通過一個布爾值控制遮罩層的顯示和隱藏。
- 好吧以上是我的意淫。試了以后是有效果。但是不完美,圖片放大事件啥的不知道咋弄,后來用了個比較成熟的PhotoSwipe,研究了一番加上自己稍微的小修改做出來了。給它個贊吧。
源碼鏈接戳這里
預覽地址戳這里~建議將瀏覽器切成手機分辨率
路過的親給個star吧~~
發布頁.jpeg
圖片上傳,可一次性上傳多張.jpeg
圖片預覽.jpeg
圖片預覽.jpeg
發布動態中.jpeg
發布成功.jpeg
//upload.vue
<!--
Description 圖片上傳
@authors Benny
@date 2018-05-08 12:57:08
@version 1.0.0
-->
<template>
<div id="imgUploader">
<div class="file-list">
<!-- <section class="file-item draggable-item" v-for="(item,index) in files" :key="index"> -->
<div class="thumbnails my-gallery">
<figure itemprop="associatedMedia" itemscope class="thumbnail" v-for="(item,index) in files" :key="index">
<a :href="item.src" itemprop="contentUrl" data-size="400x400" class="img-wrapper">
<img :src="item.src" itemprop="thumbnail" />
<!-- <div :style="{background:'url('+item.src+') no-repeat', backgroundPosition:'center center',backgroundSize:'cover'}" style="width:100%;height:110px;" itemprop="thumbnail"></div> -->
</a>
<span class="file-remove" @click="remove(index,$event)">×</span>
</figure>
<section class="thumbnail" v-if="this.files.length < 9">
<div class="add">
<span>+</span>
<!-- accept="image/jpeg,image/png" capture="camera" -->
<input type="file" @change="selectImgs()" multiple accept="image/*" ref="file">
</div>
</section>
</div>
<!-- <img :src="item.src" alt="" ondragstart="return false;">
<span class="file-remove" @click="remove(index)">×</span> -->
<!-- </section> -->
<!-- PhotoSwipe插件需要的元素, 一定要有類名 pswp -->
<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">
<div class="pswp__bg"></div>
<div class="pswp__scroll-wrap">
<div class="pswp__container">
<div class="pswp__item"></div>
<div class="pswp__item"></div>
<div class="pswp__item"></div>
</div>
<!-- 預覽區域頂部的默認UI,可以修改 -->
<div class="pswp__ui pswp__ui--hidden">
<div class="pswp__top-bar">
<!-- 與圖片相關的操作 -->
<div class="pswp__counter"></div>
<button class="pswp__button pswp__button--close" title="Close (Esc)"></button>
<!--將分享按鈕去掉 -->
<!-- <button class="pswp__button pswp__button--share" title="Share"></button>
<button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>
<button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button> -->
<div class="pswp__preloader">
<div class="pswp__preloader__icn">
<div class="pswp__preloader__cut">
<div class="pswp__preloader__donut"></div>
</div>
</div>
</div>
</div>
<div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
<div class="pswp__share-tooltip"></div>
</div>
<button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)"></button>
<button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)"></button>
<div class="pswp__caption">
<div class="pswp__caption__center"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script src='scripts/shared/upload'/>
<style src='styles/shared/upload.less' lang="less" scoped>
//upload.js
//注: photoswipe 需要npm install 安裝一下,主要是用于圖片預覽
import PhotoSwipe from "photoswipe";
import PhotoSwipeUI_Default from "photoswipe/dist/photoswipe-ui-default";
import "photoswipe/dist/photoswipe.css";
import "photoswipe/dist/default-skin/default-skin.css";
import { Toast } from 'mint-ui';//這塊可以不用,只是提示框
export default {
data() {
return {
lang: this.$lang('dynamic'),//語言包
files: [], // 文件緩存
index: 0, // 序列號
maxLength: 9, // 圖片最大數量
maxSize: 10240000, //圖片限制為10M內
};
},
methods: {
//選擇圖片
selectImgs() {
let fileList = this.$refs.file.files
if (fileList.length > 9) { //如果大于9張,做出提醒
alert(this.lang.dynamic_upload_tips)
}
let tempList = []; //每次點擊+號后選擇的圖片信息
for (let i = 0, len = fileList.length; i < len; i++) {
let fileItem = {
Id: this.index++,
name: fileList[i].name,
size: fileList[i].size,
file: fileList[i]
}
//將圖片文件轉成Base64
let reader = new FileReader()
reader.onloadend = (e) => {
//壓縮圖片并存到fileItem中
this.getBase64(e.target.result).then((url) => {
this.$set(fileItem, 'src', url)
})
}
//判斷圖片大小是否超出限制
if (fileItem.size > this.maxSize) {
Toast(this.lang.dynamic_over_size)
} else {
reader.readAsDataURL(fileList[i])
tempList.push(fileItem)
this.files.push(fileItem)
}
}
setTimeout(() => {
this.$emit('getFiles', tempList)
}, 300)
this.files.splice(9)
},
// 圖片壓縮并保存到files
getBase64(url) {
let self = this;
let Img = new Image(),
dataURL = '';
Img.src = url;
let p = new Promise(function (resolve, reject) {
Img.onload = function () { //要先確保圖片完整獲取到,這是個異步事件
let canvas = document.createElement("canvas"), //創建canvas元素
width = Img.width, //確保canvas的尺寸和圖片一樣
height = Img.height;
// 默認將長寬設置為圖片的原始長寬,這樣在長寬不超過最大長度時就不需要再處理
let ratio = width / height,
maxLength = 1000,
newHeight = height,
newWidth = width;
// 在長寬超過最大長度時,按圖片長寬比例等比縮小
if (width > maxLength || height > maxLength) {
if (width > height) {
newWidth = maxLength;
newHeight = maxLength / ratio;
} else {
newWidth = maxLength * ratio;
newHeight = maxLength;
}
}
canvas.width = newWidth;
canvas.height = newHeight;
canvas.getContext("2d").drawImage(Img, 0, 0, newWidth, newHeight); //將圖片繪制到canvas中
dataURL = canvas.toDataURL('image/jpeg', 0.5); //轉換圖片為dataURL
resolve(dataURL);
};
});
return p
},
// 移除圖片
remove(index, e) {
e.stopPropagation(); //阻止
this.files.splice(index, 1)
setTimeout(() => {
this.$emit('removeFiles', index)
}, 300)
},
//引入photoSwipe(可預覽、滑動)
initPhotoSwipeFromDOM(gallerySelector) {
var parseThumbnailElements = function (el) {
var thumbElements = el.childNodes,
numNodes = thumbElements.length,
items = [],
figureEl,
linkEl,
size,
item;
for (var i = 0; i < numNodes - 1; i++) {
figureEl = thumbElements[i];
if (figureEl.nodeType !== 1) {
continue;
}
linkEl = figureEl.children[0];
var img = new Image();
img.src = linkEl.getAttribute('href');
linkEl.setAttribute('data-size', img.naturalWidth + 'x' + img.naturalHeight);
size = linkEl.getAttribute("data-size").split("x");
item = {
src: linkEl.getAttribute("href"),
w: parseInt(size[0], 10),
h: parseInt(size[1], 10)
};
if (figureEl.children.length > 1) {
item.title = figureEl.children[1].innerHTML;
}
if (linkEl.children.length > 0) {
item.msrc = linkEl.children[0].getAttribute("src");
}
item.el = figureEl;
items.push(item);
}
return items;
};
var closest = function closest(el, fn) {
return el && (fn(el) ? el : closest(el.parentNode, fn));
};
var onThumbnailsClick = function (e) {
e = e || window.event;
// e.preventDefault ? e.preventDefault() : (e.returnValue = false);
var eTarget = e.target || e.srcElement;
var clickedListItem = closest(eTarget, function (el, e) {
return el.tagName && el.tagName.toUpperCase() === "FIGURE";
});
if (!clickedListItem) {
return;
}
var clickedGallery = clickedListItem.parentNode,
childNodes = clickedListItem.parentNode.childNodes,
numChildNodes = childNodes.length,
nodeIndex = 0,
index;
for (var i = 0; i < numChildNodes; i++) {
if (childNodes[i].nodeType !== 1) {
continue;
}
if (childNodes[i] === clickedListItem) {
index = nodeIndex;
break;
}
nodeIndex++;
}
if (index >= 0) {
openPhotoSwipe(index, clickedGallery);
}
return false;
};
var photoswipeParseHash = function () {
var hash = window.location.hash.substring(1),
params = {};
if (hash.length < 5) {
return params;
}
var vars = hash.split("&");
for (var i = 0; i < vars.length; i++) {
if (!vars[i]) {
continue;
}
var pair = vars[i].split("=");
if (pair.length < 2) {
continue;
}
params[pair[0]] = pair[1];
}
if (params.gid) {
params.gid = parseInt(params.gid, 10);
}
return params;
};
var openPhotoSwipe = function (
index,
galleryElement,
disableAnimation,
fromURL
) {
var pswpElement = document.querySelectorAll(".pswp")[0],
gallery,
options,
items;
items = parseThumbnailElements(galleryElement);
options = {
history: false,
tapToClose: true,
galleryUID: galleryElement.getAttribute("data-pswp-uid"),
getThumbBoundsFn: function (index) {
var thumbnail = items[index].el.getElementsByTagName("img")[0],
pageYScroll =
window.pageYOffset || document.documentElement.scrollTop,
rect = thumbnail.getBoundingClientRect();
return { x: rect.left, y: rect.top + pageYScroll, w: rect.width };
}
};
if (fromURL) {
if (options.galleryPIDs) {
for (var j = 0; j < items.length; j++) {
if (items[j].pid == index) {
options.index = j;
break;
}
}
} else {
options.index = parseInt(index, 10) - 1;
}
} else {
options.index = parseInt(index, 10);
}
if (isNaN(options.index)) {
return "";
}
if (disableAnimation) {
options.showAnimationDuration = 0;
}
gallery = new PhotoSwipe(
pswpElement,
PhotoSwipeUI_Default,
items,
options
);
gallery.init();
};
var galleryElements = document.querySelectorAll(gallerySelector);
for (var i = 0, l = galleryElements.length; i < l; i++) {
galleryElements[i].setAttribute("data-pswp-uid", i + 1);
galleryElements[i].onclick = onThumbnailsClick;
}
var hashData = photoswipeParseHash();
if (hashData.pid && hashData.gid) {
openPhotoSwipe(
hashData.pid,
galleryElements[hashData.gid - 1],
true,
true
);
}
}
},
mounted() {
this.initPhotoSwipeFromDOM(".my-gallery");
}
};
//upload.less
//樣式塊命名啥的不太規范.建議花小小的時間重寫下
#imgUploader {
flex: 1;
margin-top: auto;
padding-left:10px;
.file-list {
padding: 10px 0px;
&::after {
content: "";
display: block;
clear: both;
visibility: hidden;
line-height: 0;
height: 0;
font-size: 0;
}
.file-remove {
position: absolute;
font-size: 12px;
right: 5px;
top: 1px;
width: 14px;
height: 14px;
color: white;
cursor: pointer;
line-height: 12px;
background: rgba(0, 0, 0, 0.25);
z-index: 1000;
}
&:hover .file-remove {
display: inline;
}
}
}
.add {
width: 100%;
height: 110px;
float: left;
text-align: center;
line-height: 110px;
// font-size: 1.4rem;
font-weight: 100;
cursor: pointer;
border: 1px dashed #ccc;
color: #999;
position: relative;
// background: #f2f2f2;
.fa {
font-size: 1.4em;
color: #7dd2d9;
}
}
.uploadBtn {
position: relative;
.empty {
position: absolute;
right: 0;
bottom: 0;
background-color: #eee;
color: #fff;
padding: 0.2em 1em;
}
}
.thumbnails {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-wrap: wrap;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
.thumbnail {
position: relative;
margin:0 0 10px 0;
padding-right: 5px;
width: 33%;
box-sizing: border-box;
height: 110px;
// &:nth-child(3n){
// padding-right: 0;
// }
.img-wrapper{
position: relative;
display: flex;
height: 110px;
img {
width: auto;
height: auto;
width: 100%;
max-width: 100%;
max-height: 100%;
}
}
}
}
input[type="file"] {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 110px;
opacity: 0;
}
主要的上傳圖片預覽就是以上部分啦,不過真正的上傳功能還沒開始。上邊點擊+號上傳圖片后,會觸發父組件的getFiles
方法,父組件會通過getFiles
獲取組件里邊上傳圖片的信息包括base64,文件名等,然后再在里頭處理上傳操作。
//父組件 create.vue
<!--
Description 發布動態
@authors Benny
@date 2018-04-23 10:31:30
@version 1.0.0
-->
<template>
<div id="dynamic_create">
<mt-field v-model="dynamicContent" :placeholder="lang.dynamic_moments_thought" type="textarea" rows="4" ></mt-field>
<uploader @getFiles='getImageList' @removeFiles='removeImage'></uploader>
<div class="btn-wrapper">
<!-- <mt-button class='btn-send' size="large" type="primary" @click="send">{{this.lang.dynamic_release}}</mt-button> -->
<load-button :class="isSubmit ? 'text-666 btn-send' : 'bg-blue text-white btn-send'" :loading="isSubmit" @callback="send" :text="lang.dynamic_release"></load-button>
</div>
</div>
</template>
<script src='scripts/profile/dynamic/create'/>
<style src='styles/profile/dynamic/create.less' scoped lang="less"/>
//create.js
//主要是在getImageList方法
getImageList(files) {
this.$nextTick(() => {
for (let i = 0, len = files.length; i < len; i++) {
this.imgList.push(files[i].src.split('base64,')[1])
//上傳圖片
this._getFileCode({
Base64Str: files[i].src.split('base64,')[1],
AttachmentType: this.$enums.AttachmentType.Activity
})
}
})
},
以上就是核心代碼啦,有什么不對或者可以改進的地方可以評論一起探討哦~
有點瑕疵的地方就是上傳圖片預覽的時候,圖片還是有點拉伸,有木有什么辦法可以解決呢?類似微信那種 上傳完后展示裁剪圖片的中心塊。