<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
elementui
的image
模組主要是有main.vue
,image-viewer.vue兩個檔案組成的,其最外層的main.vue
是image元件的主體檔案,image-viewer.vue
檔案主要為點選圖片後的大圖預覽。
首先理清楚image
元件中完成了什麼功能,功能列表如下:
(1)image
元件能夠設定fit的屬性,通過fit
來設定圖片展示形式
(2)圖片懶載入的使用,基於scroll
卷軸事件來進行
(3)圖片的大圖預覽,通過設定preview
來開啟,同時需要傳入previewSrcList
引數。
(4)可設定圖片載入失敗的顯示,通過slot
來實現
(5)可設定圖片載入過程的顯示,通過slot
來實現
(6)圖片在大圖預覽下的功能: 縮放,旋轉,等比例調整,圖片切換,基於滑鼠滾輪的縮放,基於鍵盤輸入的縮放,切換,隱藏預覽功能,基於滑鼠按下事件的拖放功能。
fit樣式主要是包含5種形式,分別是:fill
, none
, contain
, scale-down
,cover
object-fit屬性在有些低版本的瀏覽器上是不被支援的,所以需要自己處理以上的五種顯示形式,下面會進行整理書寫
object-fit
屬性用於指定應如何調整 img
或 video
的大小以適合其容器。該屬性告訴內容以多種方式填充容器;例如“保持縱橫比”或“拉伸並佔用儘可能多的空間”。具體的圖片展示效果如下圖:
根據MDN-CSS的解釋,以上5種形式的對應解釋如下
contain:
被替換的內容將被縮放,以在填充元素的內容框時保持其寬高比。 整個物件在填充盒子的同時保留其長寬比,因此如果寬高比與框的寬高比不匹配,該物件將被新增==黑邊==。
cover:
被替換的內容在保持其寬高比的同時填充元素的整個內容框。如果物件的寬高比與內容框不相匹配,該物件將被剪裁以適應內容框。
fill:
被替換的內容正好填充元素的內容框。整個物件將完全填充此框。如果物件的寬高比與內容框不相匹配,那麼該物件將被拉伸以適應內容框。
none:
被替換的內容將保持其原有的尺寸。
scale-down:
內容的尺寸與
none
或contain
中的一個相同,取決於它們兩個之間誰得到的物件尺寸會更小一些。
那麼在image元件中,為了實現fit
的動態設定,image元件中是由父元件傳入fit
的屬性值,如果不傳入的話,會選擇預設的fill
,來對圖片進行處理。
同時由於部分低版本瀏覽器平臺中,是不支援object-fit
的,所以image元件對其進行了處理,通過一個computed
資料imageStyle
來控制image的樣式。同時對於不支援的瀏覽器平臺,也自定義了一份類object-fit
的樣式來進行相容。
具體相容程式碼如下:
//通過這段程式碼來判斷是否支援當前瀏覽器,如果不支援就返回false,支援返回true const isSupportObjectFit = () => document.documentElement.style.objectFit !== undefined; //定義列舉類,用於對應fit的幾種形式 const ObjectFit = { NONE: 'none', CONTAIN: 'contain', COVER: 'cover', FILL: 'fill', SCALE_DOWN: 'scale-down' }; /** * 相容程式碼,它的實際實現也很簡單,實際就是處理圖片的長寬比,來控制圖片的顯示格式 */ getImageStyle(fit) { const { imageWidth, imageHeight } = this; const { clientWidth: containerWidth, clientHeight: containerHeight } = this.$el; if (!imageWidth || !imageHeight || !containerWidth || !containerHeight) return {}; const imageAspectRatio = imageWidth / imageHeight; const containerAspectRatio = containerWidth / containerHeight; if (fit === ObjectFit.SCALE_DOWN) { const isSmaller = imageWidth < containerWidth && imageHeight < containerHeight; fit = isSmaller ? ObjectFit.NONE : ObjectFit.CONTAIN; } switch (fit) { case ObjectFit.NONE: return { width: 'auto', height: 'auto' }; case ObjectFit.CONTAIN: return (imageAspectRatio < containerAspectRatio) ? { width: 'auto' } : { height: 'auto' }; case ObjectFit.COVER: return (imageAspectRatio < containerAspectRatio) ? { height: 'auto' } : { width: 'auto' }; default: return {}; } } /** * 圖片的長寬獲取實際是在mounted種獲取到的,通過新建一個Image類,然後監控imageLoad是否成功,如果成功了,就可以讀取圖片的長寬。 */ loadImage() { if (this.$isServer) return; // reset status this.loading = true; this.error = false; const img = new Image(); img.onload = e => this.handleLoad(e, img); img.onerror = this.handleError.bind(this); // bind html attrs // so it can behave consistently Object.keys(this.$attrs) .forEach((key) => { const value = this.$attrs[key]; img.setAttribute(key, value); }); img.src = this.src; } handleLoad(e, img) { this.imageWidth = img.width; this.imageHeight = img.height; this.loading = false; this.error = false; } //computed屬性imageStyle imageStyle() { const { fit } = this; if (!this.$isServer && fit) { return isSupportObjectFit() ? { 'object-fit': fit } : this.getImageStyle(fit); } return {}; }
通過上述的程式碼,就可以設定圖片的fit
屬性了,如果說瀏覽器不支援,也可通過自定義width
與height
來實現類fit的樣式。雖然說實現起來的步驟比較多,但是這樣可以為大部分瀏覽器平臺提供很好的適配和相容,是一種很好的處理方式。
圖片懶載入的實現實際上用了節流器來完成,通過設定延遲器,避免在第一時間載入圖片,等待一個指定的時間後載入圖片,達到圖片懶載入的實現。具體的防抖和節流的實現,這裡就先不贅述了,等到之後出一片博文之後,來在這裡新增一條連結。
同時這裡的圖片懶載入也是基於Scroll的事件監聽進行實現,然後加入throttle之後,設定等待時間為200ms,來實現show的延遲載入。
具體實現程式碼如下:
addLazyLoadListener() { //判斷是否是伺服器端渲染,如果是伺服器端渲染就無法使用懶載入,因為伺服器端渲染是把所有標籤全部渲染完成後輸出的 if (this.$isServer) return; const { scrollContainer } = this; let _scrollContainer = null; if (isHtmlElement(scrollContainer)) { _scrollContainer = scrollContainer; } else if (isString(scrollContainer)) { _scrollContainer = document.querySelector(scrollContainer); } else { _scrollContainer = getScrollContainer(this.$el); } if (_scrollContainer) { this._scrollContainer = _scrollContainer; //這裡來設定節流,等待時間為200ms,這樣就不會實時出發scroll捲動事件。 this._lazyLoadHandler = throttle(200, this.handleLazyLoad); on(_scrollContainer, 'scroll', this._lazyLoadHandler); this.handleLazyLoad(); } }
實現還是比較簡單的,但是這裡的很多東西感覺很值得學習,無論是節流還是捲動事件的設定。作為現在的新手的我來說是值得學習的,又可以學習使用節流的場景,也可以學習節流與事件合併使用的技巧。
這裡的圖片載入失敗和載入中的可設定實現,其實都是基於slot進行實現的,也就是vue中插槽,並且這裡使用了具名插槽的使用。插槽的使用還是比較簡單的,這裡就放一下vue官網對插槽的介紹:vue插槽
先放一張圖,通過這個圖裡的內容來介紹預覽功能
圖片預覽模組的功能實現就比較好玩了,主要分為上面說的幾個方面,現在就來說一下這些方面:
佈局:
頁面採用的佈局實際就是蒙版的實現,但是image-view
的蒙板實現其實存在一些問題,就是當把元件插在頁面中的時候,如果整個頁面是很長的頁面,那麼在對圖片使用滾輪縮放放大的時候,主頁面也會被影響。整體的頁面佈局採用的還是flex
佈局的形式,同時將justify-content
設定問center
,align-item
也設定為center
,這樣就可以保證image
的居中顯示了。其他的圖示都為absolute
,圖示組依然在absolute
的基礎上,繼續設定了flex
佈局,同時為了保證每一個圖示在圖示組內均為分佈,justify-content
設定為了space-around
。
flex佈局牛逼,節省了好多時間,只需要調解基線就完事了。
放縮
圖片放縮的功能是基於css中transform
屬性,transform
屬性提供了scale
的調整,所以很方便就實現了圖片的放縮,同時在圖片使用transform
的時候,會被預設提升到最頂層,這就完美的解決了蒙版遮蓋的問題,一舉兩得。基礎的放縮在image元件的控制是基於兩個放縮按鈕來實現的,所以也是很方便的。
旋轉
圖片旋轉的功能是基於css中transform
屬性,transform
屬性提供了rotate
的調整,所以很方便就實現了圖片的旋轉。
在旋轉與放縮的實現中,會設定一個判斷是否是通過普通按鈕操作的判斷,如果是普通的操作就會再加上一個
transition
來設定切換的時間,讓互動更加友好。但是在滾輪的縮放中,就需要取消transition
屬性,因為滾輪的滑動十分迅速,互動十分頻繁,為了達到節流的要求,就需要設定滾輪每一次捲動的步長比普通按鈕的長,且取消transition
,防止直接卡死。
等比例調整
等比例調整功能實現也很簡單,只需要原本設定的max-width
與max-height
直接取消掉就可以實現了。
const Mode = { CONTAIN: { name: 'contain', icon: 'el-icon-full-screen' }, ORIGINAL: { name: 'original', icon: 'el-icon-c-scale-to-original' } }; toggleMode() { if (this.loading) return; const modeNames = Object.keys(Mode); const modeValues = Object.values(Mode); const index = modeValues.indexOf(this.mode); const nextIndex = (index + 1) % modeNames.length; this.mode = Mode[modeNames[nextIndex]]; this.reset(); }
通過按鈕事件,來切換class即可
圖片切換
通過鍵盤輸入的左右方向鍵或者頁面中提供的按鈕來進行圖片切換的操作。這裡圖片切換也就是來通過修改修改currentImage以及指向srclist的下標的改變來進行切換。同時這裡還有在載入時的一步操作就是在一開始的獲取點的image的在srclist中的位置來初始化下表。
//currentImage與viewerZIndex是一個computed屬性,這樣就不需要可以修改這兩個屬性,一旦發生改變就可以觸發 computed: { currentImg() { return this.urlList[this.index]; }, viewerZIndex() { const nextZIndex = PopupManager.nextZIndex(); return this.zIndex > nextZIndex ? this.zIndex : nextZIndex; } } //具體的實現 methods: { prev() { if (this.isFirst && !this.infinite) return; const len = this.urlList.length; this.index = (this.index - 1 + len) % len; }, next() { if (this.isLast && !this.infinite) return; const len = this.urlList.length; this.index = (this.index + 1) % len; } }
基於滑鼠滾輪的放縮
基於滑鼠滾輪的放縮就是對當前的頁面新增了一個滑鼠滾輪滑動的事件,同時通過window.requestAnimationFrame來優化滑鼠滾輪滑動的效率,讓其事件的觸發與瀏覽器重新整理頻率相同,優化效能。
對於滑鼠滾輪的事件,在不同的瀏覽器中擁有不同的事件相應名稱,在image元件中的處理就是判斷isFirefox
,具體程式碼如下
export const isFirefox = function() { return !Vue.prototype.$isServer && !!window.navigator.userAgent.match(/firefox/i); }; const mousewheelEventName = isFirefox() ? 'DOMMouseScroll' : 'mousewheel';
不過這裡個人感覺還不是特別嚴謹,感覺mdn上給出的判斷更加合理,這裡貼出程式碼
//獲取滑鼠滾輪事件 const mouseWheel = () => "onwheel" in document.createElement("div") ? "wheel" // 各個廠商的高版本瀏覽器都支援"wheel" : document.onmousewheel !== undefined ? "mousewheel" // Webkit 和 IE一定支援"mousewheel" : "DOMMouseScroll"; // 低版本firefox在自己基於自己的blog開發的image元件中,我才用了上述的寫法, 並做了測試,效果還不錯啦
這裡讓我主要學習到的是滑鼠滾輪事件的優化,就是上面說的結合window.requestAnimationFrame來使用的方法,真的感覺很棒,這樣每一次滾輪事件的觸發,都結合了當前螢幕的實時重新整理率,讓其效果達到最好。
具體的程式碼實現貼出:
this.mouseWheelHandler = rafThrottle((e) => { /** * NOTE: wheelDelta和detail都是用來判斷滾輪是上滑還是下滑,但是在不同瀏覽器的平臺中的判斷條件不同,所以需要兩個都叫判斷 * NOTE: 當使用window.requestAnimationFrame的時候,是可以不使用transition的 */ const delta = e.wheelDelta ? e.wheelDelta : -e.detail; if (delta > 0) { this.transform.enableTransition = false; this.zoomIn(false); } else { this.transform.enableTransition = false; this.zoomOut(false); } }); //on方法是elementui封裝的一個柯里化的addEventListener方法 on(this.$isServer)(document, mouseWheel(), this.mouseWheelHandler); export function rafThrottle(fn) { //這裡的locked是限制了當前頁面的requestAnimationFrame事件只能存在一個的鎖。 let locked = false; return function(...args) { if (locked) return; locked = true; window.requestAnimationFrame(_ => { fn.apply(this, args); locked = false; }); }; }
基於鍵盤輸入的縮放
基於鍵盤的縮放就是對鍵盤輸入做了監聽,並且對上下左右四個方向鍵以及esc鍵、空格做了處理。直接貼程式碼好了,很簡單
this._keyDownHandler = e => { //防止事件向上傳播,只在當前標籤中使用 e.stopPropagation(); const keyCode = e.keyCode; switch (keyCode) { // ESC case 27: this.hide(); break; // SPACE case 32: this.toggleMode(); break; // LEFT_ARROW case 37: this.prev(); break; // UP_ARROW case 38: this.handleActions('zoomIn'); break; // RIGHT_ARROW case 39: this.next(); break; // DOWN_ARROW case 40: this.handleActions('zoomOut'); break; } };
隱藏預覽功能
隱藏預覽主要有三種形式,一種是按下esc鍵,一種是點選蒙版而不是圖片的位置,最後一種就是點選按鈕,其實現都是完全相同,這裡我有點不明白為什麼要把方法作為prop傳過來,然後再執行,為什麼不可以做為emit
來處理。實現還是很簡單,就不貼了。
基於滑鼠按下事件的拖放功能
滑鼠按下事件的拖放功能主要依賴於vue中支援的mousedown事件來實現,同時在mousedown的同時,繫結上mousemove事件,來獲取移動的x,y軸的位置,給到transform上的offsetX與offsetY屬性,用來設定imageStyle的margin-left,margin-top屬性。其實還是比較簡單的。
具體程式碼實現如下:
handleMouseDown(e) { if (this.loading || e.button !== 0) return; const { offsetX, offsetY } = this.transform; const startX = e.pageX; const startY = e.pageY; this._dragHandler = rafThrottle(ev => { this.transform.offsetX = offsetX + ev.pageX - startX; this.transform.offsetY = offsetY + ev.pageY - startY; }); on(document, 'mousemove', this._dragHandler); on(document, 'mouseup', ev => { off(document, 'mousemove', this._dragHandler); }); //當事件處理結束後,就會將本事件取消,不會進一步發生 e.preventDefault(); }
elementui的事件管理都是通過一個柯里化的方法來進行的,就是src/utils/dom.js下的on和off方法,這兩個方法寫的十分巧妙,可以通過判斷是不是伺服器端渲染的程式碼,返回不同的事件繫結的程式碼,具體實現如下:
export const on = function (isServer) { if (!isServer && document.addEventListener) { return function (element, event, handle) { if (element && event) { element.addEventListener(event, handle, false) } } } else { return function (element, event, handle) { if (element && event) { element.attachEvent(`on${event}`, handle) } } } } export const off = function (isServer) { if (!isServer && document.addEventListener) { return function (element, event, handle) { if (element && event) { element.removeEventListener(event, handle, false) } } } else { return function (element, event, handle) { if (element && event) { element.detachEvent(`on${event}`, handle) } } } }
雖然這對繫結事件來說會變得十分簡單,但是還是有一個關鍵的問題就是我無法知道我當前事件是否被消除,所以很想知道有沒有很好的事件管理機制來負責處理這些被建立的事件,然後再不需要在使用這些的事件時候,去處理掉這些事件,這也是之後需要我去探索的內容。
element-ui的image元件的原始碼閱讀是我第一個仔細分析的它執行的每一步的一個元件,之前雖然也看過了兩個比較複雜模組的原始碼 table
以及 tree
的實現,但是卻沒有這次這麼仔細地去分析他的每一個步驟以及頁面的佈局設計。在本次閱讀的過程中,學到了怎麼去優化頻繁互動的處理方法,以及防抖和節流的使用
到此這篇關於elementui下image元件的使用的文章就介紹到這了,更多相關element image元件內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45