vue.js 3.2.20: 用photoswipe实现图片的浏览:增加切换动画和自动播放(photoswipe@4.1.3)
一,安装photoswipe
liuhongdi@lhdpc:/data/vue/swipe$ npm i photoswipe --save up to date in 6s 15 packages are looking for funding run `npm fund` for details
另:photoswipe的官网:
https://photoswipe.com/
此demo项目的源码:
https://gitee.com/liuhongdi/swipe
说明:刘宏缔的架构森林是一个专注架构的博客,
网站:https://blog.imgtouch.com
本文: https://blog.imgtouch.com/index.php/2023/05/28/vue-js-3-2-20-yong-photoswipe-shi-xian-tu-pian-de-liu-lan/
对应的源码可以访问这里获取: https://github.com/liuhongdi/
或: https://gitee.com/liuhongdi
说明:作者:刘宏缔 邮箱: 371125307@qq.com
二,编写代码:
1,components/PhotoSwipe.vue
说明:增加了播放图片的功能
<template> <!-- 这段html代码,是用来显示图片弹出层的,是由photoswipe提供的 --> <!-- Root element of PhotoSwipe. Must have class pswp. --> <div class="pswp" tabindex="-1" role="dialog" aria-hidden="true"> <!-- Background of PhotoSwipe. It's a separate element as animating opacity is faster than rgba(). --> <div class="pswp__bg"></div> <!-- Slides wrapper with overflow:hidden. --> <div class="pswp__scroll-wrap"> <!-- Container that holds slides. PhotoSwipe keeps only 3 of them in the DOM to save memory. Don't modify these 3 pswp__item elements, data is added later on. --> <div class="pswp__container"> <div class="pswp__item"></div> <div class="pswp__item"></div> <div class="pswp__item"></div> </div> <!-- Default (PhotoSwipeUI_Default) interface on top of sliding area. Can be changed. --> <div class="pswp__ui pswp__ui--hidden"> <div class="pswp__top-bar"> <!-- Controls are self-explanatory. Order can be changed. --> <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> <!-- <button >放大</button> <button >缩小</button> <button >复原</button> <button class="pswp__button" title="play">play</button> --> <div style="color:#ffffff;float:right;margin-right:16px;margin-top:14px;" @click="play"> <img id="playbutton" src="/static/img/play.png" style="width:16px;"/> </div> <!-- Preloader demo https://codepen.io/dimsemenov/pen/yyBWoR --> <!-- element will get class pswp__preloader--active when preloader is running --> <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> </template> <script> //import { getCurrentInstance } from 'vue' // 引入photoswipe的js和css文件 import PhotoSwipe from 'photoswipe/dist/photoswipe' import PhotoSwipeUI_Default from 'photoswipe/dist/photoswipe-ui-default' import 'photoswipe/dist/photoswipe.css' import 'photoswipe/dist/default-skin/default-skin.css' export default { name: 'ZeroPhotoSwipe', data:function () { return { num:123, gallery:null, isPlaying:false, interval:null, } }, methods: { //自动播放执行的内容 autoplay:function() { let _self = this; _self.gallery.next(); }, //停止播放 stop:function() { clearInterval(this.interval); this.isPlaying = false; this.cleartransition(); document.getElementById('playbutton').src="/static/img/play.png"; }, //关闭transition cleartransition:function () { let containers = document.getElementsByClassName("pswp__container"); let container = containers[0]; container.style.transition = ''; }, //播放图片列表 play:function() { if (this.isPlaying === true) { this.stop(); } else { this.isPlaying = true; //console.log(this.gallery); document.getElementById('playbutton').src="/static/img/stop.png"; this.interval = setInterval(this.autoplay,2000); } }, //判断是否移动端的函数 isMobileFunc:function() { let flag = navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i); if (flag === null) { return 0; } else { return 1; } }, /** * 父组件调用初始化图片弹出层 * photoswipe提供的方法,这里没有做任何修改,直接拿来用了 * @param gallerySelector */ initPhotoSwipeFromDOM:function(gallerySelector) { let _self = this; console.log('_self'); console.log(_self); // parse slide data (url, title, size ...) from DOM elements // (children of gallerySelector) var parseThumbnailElements = function (el) { var thumbElements = el.childNodes, numNodes = thumbElements.length, items = [], figureEl, linkEl, //divEl, size, item; for (var i = 0; i < numNodes; i++) { figureEl = thumbElements[i]; // <figure> element // include only element nodes if (figureEl.nodeType !== 1) { continue; } linkEl = figureEl.children[0]; // <a> element //divEl = linkEl.children[0]; console.log("linkEl:"); console.log(linkEl); if(typeof(linkEl)=="undefined"){ //alert("没有定义"); //autoAddLine(formId); continue; } size = linkEl.getAttribute('data-size').split('x'); console.log("size:w:"+size[0]); console.log("size:h:"+size[1]); let w = window.innerWidth; let dest_width = w; if (size[0] == 10 || size[1] == 10) { let imgEl = linkEl.children[0]; let rect = imgEl.getBoundingClientRect(); console.log(rect); let dest_height = dest_width * rect.height / rect.width; size[0] = dest_width; size[1] = dest_height; } else { let dest_height = dest_width * size[1] / size[0]; size[0] = dest_width; size[1] = dest_height; } console.log('w:'+size[0]+";h:"+size[1]); item = { src: linkEl.getAttribute('href'), w: parseInt(size[0], 10), h: parseInt(size[1], 10) }; if (figureEl.children.length > 1) { // <figcaption> content item.title = figureEl.children[1].innerHTML; } if (linkEl.children.length > 0) { // <img> thumbnail element, retrieving thumbnail url item.msrc = linkEl.children[0].getAttribute('src'); } item.el = figureEl; // save link to element for getThumbBoundsFn items.push(item); } return items; }; // find nearest parent element var closest = function closest(el, fn) { return el && ( fn(el) ? el : closest(el.parentNode, fn) ); }; // triggers when user clicks on thumbnail var onThumbnailsClick = function (e) { e = e || window.event; e.preventDefault ? e.preventDefault() : e.returnValue = false; var eTarget = e.target || e.srcElement; // find root element of slide var clickedListItem = closest(eTarget, function (el) { return (el.tagName && el.tagName.toUpperCase() === 'FIGURE'); }); if (!clickedListItem) { return; } // find index of clicked item by looping through all child nodes // alternatively, you may define index via data- attribute var clickedGallery = clickedListItem.parentNode, childNodes = clickedListItem.parentNode.childNodes, numChildNodes = childNodes.length, nodeIndex = 0, index; for (var i = 0; i < numChildNodes; i++) { //console.log("i:"+i); //console.log("nodeType:"+childNodes[i].nodeType); //console.log(childNodes[i].nodeName); if (childNodes[i].nodeType !== 1) { continue; } if (childNodes[i].nodeName !== 'FIGURE') { continue; } if (childNodes[i] === clickedListItem) { index = nodeIndex; break; } nodeIndex++; } if (index >= 0) { // open PhotoSwipe if valid index found //console.log('begin openPhotoSwipe 1'); //const instance = getCurrentInstance() //const _this= instance.appContext.config.globalProperties; //console.log('_self:'); //console.log(_self); _self.gallery = openPhotoSwipe(index, clickedGallery); //console.log('this gallery'); //console.log(_self.gallery); _self.gallery.listen('destroy', function() { if (_self.isPlaying) { _self.stop(); } //alert('is destroy'); }); } return false; }; // parse picture index and gallery index from URL (#&pid=1&gid=2) 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) { console.log('openPhotoSwipe'); var pswpElement = document.querySelectorAll('.pswp')[0], gallery, options, items; items = parseThumbnailElements(galleryElement); /* * getThumbBoundsFn:function(){ return {x:$self.left,y:$self.top,w:self[0].naturalWidth}; //动画开始时从对应的图片放大到全屏,返回对应图片相对于窗口的实际坐标和自己的宽度 }, maxSpreadZoom:2.5,//手势放大图片最大倍数 allowPanToNext:true,//图片处于放大状态是否允许滑动到下一张 getDoubleTapZoom:function(){ return 1;//双击后图片缩放到的倍数//1表示缩放到原始大小 }, loop:false,//滑动到最后一张是否可以继续循环到第一张 history: false, focus: false, //closeOnVerticalDrag:false,//垂直拖动图片关闭弹层 spacing:0.03, showAnimationDuration: 430,//显示大图动画时间 hideAnimationDuration: 430,//隐藏大图动画时间 showHideOpacity:true,//动画时淡出逐渐变透明 index: index // 从哪一张图片开始 * */ // define options (if needed) options = { shareButtons: [ {id:'wechat', label:'分享微信', url:'#'}, {id:'weibo', label:'新浪微博', url:'#'}, {id:'download', label:'保存图片', url:'{{raw_image_url}}', download:true} ], // define gallery index (for URL) galleryUID: galleryElement.getAttribute('data-pswp-uid'), maxSpreadZoom:2.5, getThumbBoundsFn: function (index) { // See Options -> getThumbBoundsFn section of documentation for more info var thumbnail = items[index].el.getElementsByTagName('img')[0], // find thumbnail pageYScroll = window.pageYOffset || document.documentElement.scrollTop, rect = thumbnail.getBoundingClientRect(); return {x: rect.left, y: rect.top + pageYScroll, w: rect.width}; } }; // PhotoSwipe opened from URL if (fromURL) { if (options.galleryPIDs) { // parse real index when custom PIDs are used // http://photoswipe.com/documentation/faq.html#custom-pid-in-url for (var j = 0; j < items.length; j++) { if (items[j].pid == index) { options.index = j; break; } } } else { // in URL indexes start from 1 options.index = parseInt(index, 10) - 1; } } else { options.index = parseInt(index, 10); } // exit if index not found if (isNaN(options.index)) { return; } if (disableAnimation) { options.showAnimationDuration = 0; } //options. // Pass data to PhotoSwipe and initialize it gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, items, options); gallery.init(); return gallery; }; // loop through all gallery elements and bind events 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; } // Parse URL and open gallery if it contains #&pid=3&gid=1 var hashData = photoswipeParseHash(); if (hashData.pid && hashData.gid) { console.log('begin openPhotoSwipe 2'); _self.gallery = openPhotoSwipe(hashData.pid, galleryElements[hashData.gid - 1], true, true); //this.gallery = gallery; _self.gallery.listen('destroy', function() { if (_self.isPlaying) { _self.stop(); } //alert('is destroy'); }); } } }, } </script>
2,components/Home.vue
<template> <div> <zero-photo-swipe ref="zeroPhoto"></zero-photo-swipe> <div class="my-gallery"> <figure style="float:left;margin-inline-start:0px;margin-inline-end:0px;"> <a href="/static/img/b.jpg" data-size="10x10"> <img @mouseover="imgMouseOver(1)" id="img1" src="/static/img/b.jpg" style="max-width:100%;float:left;height:250px;border-radius: 5px;" v-on:load="loadImgSize($event)"/> <div id="mask1" @mouseout="imgMouseOut(1)" style="display:none;width:100%;height:100%;background: #ffffff;opacity: 0.4;position: absolute;top:0px;left: 0px;"> <img id="mag1" src="@/assets/img/mag.png" style="width:60px;height:60px;"/> </div> </a> </figure> <!--<div style="width:30px;height:100px;margin-left:30px;float:left;background: #ff0000;"></div>--> <figure style="float:left;margin-inline-start:0px;margin-inline-end:0px;"> <a href="/static/img/c.jpg" data-size="10x10"> <img @mouseover="imgMouseOver(2)" id="img2" src="/static/img/c.jpg" style="max-width:100%;float:left;height:250px;margin-left: 20px;border-radius: 5px;" v-on:load="loadImgSize($event)"/> <div id="mask2" @mouseout="imgMouseOut(2)" style="display:none;width:100%;height:100%;background: #ffffff;opacity: 0.4;position: absolute;top:0px;left: 0px;"> <img id="mag2" src="@/assets/img/mag.png" style="width:60px;height:60px;"/> </div> </a> </figure> <figure style="float:left;margin-inline-start:0px;margin-inline-end:0px;"> <a href="/static/img/tbh.jpeg" data-size="10x10"> <img @mouseover="imgMouseOver(3)" id="img3" src="/static/img/tbh.jpeg" style="max-width:100%;float:left;height:250px;margin-left: 20px;border-radius: 5px;" v-on:load="loadImgSize($event)"/> <div id="mask3" @mouseout="imgMouseOut(3)" style="display:none;width:100%;height:100%;background: #ffffff;opacity: 0.4;position: absolute;top:0px;left: 0px;"> <img id="mag3" src="@/assets/img/mag.png" style="width:60px;height:60px;"/> </div> </a> </figure> <figure style="float:left;margin-inline-start:0px;margin-inline-end:0px;"> <a href="/static/img/qx.jpeg" data-size="10x10"> <img @mouseover="imgMouseOver(4)" id="img4" src="/static/img/qx.jpeg" style="max-width:100%;float:left;height:250px;margin-left: 20px;border-radius: 5px;" v-on:load="loadImgSize($event)"/> <div id="mask4" @mouseout="imgMouseOut(4)" style="display:none;width:100%;height:100%;background: #ffffff;opacity: 0.4;position: absolute;top:0px;left: 0px;"> <img id="mag4" src="@/assets/img/mag.png" style="width:60px;height:60px;"/> </div> </a> </figure> <figure style="float:left;margin-inline-start:0px;margin-inline-end:0px;"> <a href="/static/img/lb.jpeg" data-size="10x10"> <img @mouseover="imgMouseOver(5)" id="img5" src="/static/img/lb.jpeg" style="max-width:100%;float:left;height:250px;margin-left: 20px;border-radius: 5px;" v-on:load="loadImgSize($event)"/> <div id="mask5" @mouseout="imgMouseOut(5)" style="display:none;width:100%;height:100%;background: #ffffff;opacity: 0.4;position: absolute;top:0px;left: 0px;"> <img id="mag5" src="@/assets/img/mag.png" style="width:60px;height:60px;"/> </div> </a> </figure> <figure style="float:left;margin-inline-start:0px;margin-inline-end:0px;"> <a href="/static/img/gy.jpeg" data-size="10x10"> <img @mouseover="imgMouseOver(6)" id="img6" src="/static/img/gy.jpeg" style="max-width:100%;float:left;height:250px;margin-left: 20px;border-radius: 5px;" v-on:load="loadImgSize($event)"/> <div id="mask6" @mouseout="imgMouseOut(6)" style="display:none;width:100%;height:100%;background: #ffffff;opacity: 0.4;position: absolute;top:0px;left: 0px;"> <img id="mag6" src="@/assets/img/mag.png" style="width:60px;height:60px;"/> </div> </a> </figure> </div> </div> </template> <script> import ZeroPhotoSwipe from "../components/PhotoSwipe.vue"; export default { name: 'Gallery', components: { ZeroPhotoSwipe }, mounted(){ // 调用初始化图片photoswipe this.$refs.zeroPhoto.initPhotoSwipeFromDOM('.my-gallery') }, methods:{ /** * 加载图片结束时自动添加photoswipe需要的data-size * @param e */ loadImgSize(e) { var target = e.currentTarget var x = target.clientWidth var y = target.clientHeight target.dataSize = `${x}x${y}` target.parentNode.setAttribute('data-size', `${x}x${y}`) }, imgMouseOver(id) { //alert(id); //let idmask="mask"+id; let rect = document.getElementById("img"+id).getBoundingClientRect(); console.log(rect); let mask = document.getElementById("mask"+id); mask.style.left = rect.left +'px'; mask.style.top = rect.top +'px'; mask.style.width = rect.width +'px'; mask.style.height = rect.height +'px'; //mask.style.lineHeight = rect.height +'px'; let mag = document.getElementById("mag"+id); mag.style.marginTop = (rect.height-60)/2 +'px'; mask.style.display = ""; }, imgMouseOut(id) { let mask = document.getElementById("mask"+id); mask.style.display = "none"; }, } } </script>
三,修改photoswipe.js的代码
说明:主要是为了切换图片时的动画效果:
只修改next()和prev()两个方法即可
next: function() { console.log('next'); let tf = self.container.style.transform; //设置目标位置 let arr1 = tf.split("("); let str1 = arr1[1]; //console.log("str1:"+str1); let arr2 = str1.split("px"); let curleft = arr2[0]; let w = _slideSize.x; let dest_left = parseInt(curleft)-w; self.container.style.transform = 'translate3d(' + dest_left + 'px, 0px, 0px)'; self.container.style.transition = 'all 0.5s ease-in-out'; setTimeout(function(){ console.log('setTimeout begin'); self.container.style.transition = 'all 0s ease-in-out'; self.goTo( _currentItemIndex + 1); },550); }, prev: function() { console.log('prev'); let tf = self.container.style.transform; //设置目标位置 let arr1 = tf.split("("); let str1 = arr1[1]; //console.log("str1:"+str1); let arr2 = str1.split("px"); let curleft = arr2[0]; let w = _slideSize.x; console.log("curleft:"+curleft+";w:"+w); let dest_left = parseInt(curleft)+w; console.log("dest_left:"+dest_left); self.container.style.transform = 'translate3d(' + dest_left + 'px, 0px, 0px)'; self.container.style.transition = 'all 0.5s ease-in-out'; setTimeout(function(){ console.log('setTimeout begin'); self.container.style.transition = 'all 0s ease-in-out'; self.goTo( _currentItemIndex - 1); },550); },
四,查看效果:
五,查看版本:
查看vue的版本:
liuhongdi@lhdpc:/data/vue/swipe$ npm list vue swipe@0.1.0 /data/vue/swipe ├─┬ @vue/cli-plugin-babel@4.5.13 │ └─┬ @vue/babel-preset-app@4.5.13 │ └── vue@3.2.20 deduped └─┬ vue@3.2.20 └─┬ @vue/server-renderer@3.2.20 └── vue@3.2.20 deduped
查看photoswipe的版本:
liuhongdi@lhdpc:/data/vue/swipe$ npm list photoswipe swipe@0.1.0 /data/vue/swipe ├── photoswipe@4.1.3