很早就想分享给大家了。由于比较忙且懒-,
之前开发有这样一个需求,像微信朋友圈一样发布动态的一个功能。刚开始想尝试百度下有没有合适的插件。百度了很久都没找到合适的,大都是可以上传不能预览。或者是预览功能有问题。无奈之下,还是靠自己吧。
其实想一想还是挺简单,上传就不用说了通过input的file获取到文件再通过fileReader将图片路径转成Base64。比较大的难题是点击图片预览,且可以滑动。
- 基本思路就是先写好基本样式(遮罩啊等等)然后通过当前图片点击获取到图片路径的集合,拿到集合后写个轮播,并且通过一个布尔值控制遮罩层的显示和隐藏。
- 好吧以上是我的意淫。试了以后是有效果。但是不完美,图片放大事件啥的不知道咋弄,后来用了个比较成熟的PhotoSwipe,研究了一番加上自己稍微的小修改做出来了。给它个赞吧。
源码链接戳这里
预览地址戳这里~建议将浏览器切成手机分辨率
路过的亲给个star吧~~
图片预览.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
})
}
})
},
以上就是核心代码啦,有什么不对或者可以改进的地方可以评论一起探讨哦~
有点瑕疵的地方就是上传图片预览的时候,图片还是有点拉伸,有木有什么办法可以解决呢?类似微信那种 上传完后展示裁剪图片的中心块。
网友评论