elementui下image組件的使用
elementui-image組件的組成
elementui
的image
模塊主要是有main.vue
,image-viewer.vue兩個文件組成的,其最外層的main.vue
是image組件的主體文件,image-viewer.vue
文件主要為點擊圖片后的大圖預覽。
elementui-image組件的功能
首先理清楚
image
組件中完成了什么功能,功能列表如下:? (1)
image
組件能夠設置fit的屬性,通過fit
來設置圖片展示形式? (2)圖片懶加載的使用,基于
scroll
滾動條事件來進行? (3)圖片的大圖預覽,通過設置
preview
來開啟,同時需要傳入previewSrcList
參數(shù)。? (4)可配置圖片加載失敗的顯示,通過
slot
來實現(xiàn)? (5)可配置圖片加載過程的顯示,通過
slot
來實現(xiàn)? (6)圖片在大圖預覽下的功能: 縮放,旋轉,等比例調整,圖片切換,基于鼠標滾輪的縮放,基于鍵盤輸入的縮放,切換,隱藏預覽功能,基于鼠標按下事件的拖放功能。
elementui-image的功能實現(xiàn)
fit的樣式實現(xiàn)
fit樣式主要是包含5種形式,分別是:fill
, none
, contain
, scale-down
,cover
object-fit屬性在有些低版本的瀏覽器上是不被支持的,所以需要自己處理以上的五種顯示形式,下面會進行整理書寫
object-fit
屬性用于指定應如何調整 img
或 video
的大小以適合其容器。該屬性告訴內容以多種方式填充容器;例如“保持縱橫比”或“拉伸并占用盡可能多的空間”。具體的圖片展示效果如下圖:
根據(jù)MDN-CSS的解釋,以上5種形式的對應解釋如下
contain:
被替換的內容將被縮放,以在填充元素的內容框時保持其寬高比。 整個對象在填充盒子的同時保留其長寬比,因此如果寬高比與框的寬高比不匹配,該對象將被添加==黑邊==。
cover:
被替換的內容在保持其寬高比的同時填充元素的整個內容框。如果對象的寬高比與內容框不相匹配,該對象將被剪裁以適應內容框。
fill:
被替換的內容正好填充元素的內容框。整個對象將完全填充此框。如果對象的寬高比與內容框不相匹配,那么該對象將被拉伸以適應內容框。
none:
被替換的內容將保持其原有的尺寸。
scale-down:
內容的尺寸與
none
或contain
中的一個相同,取決于它們兩個之間誰得到的對象尺寸會更小一些。
那么在image組件中,為了實現(xiàn)fit
的動態(tài)配置,image組件中是由父組件傳入fit
的屬性值,如果不傳入的話,會選擇默認的fill
,來對圖片進行處理。
同時由于部分低版本瀏覽器平臺中,是不支持object-fit
的,所以image組件對其進行了處理,通過一個computed
數(shù)據(jù)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' }; /** * 兼容代碼,它的實際實現(xiàn)也很簡單,實際就是處理圖片的長寬比,來控制圖片的顯示格式 */ 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類,然后監(jiān)控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
來實現(xiàn)類fit的樣式。雖然說實現(xiàn)起來的步驟比較多,但是這樣可以為大部分瀏覽器平臺提供很好的適配和兼容,是一種很好的處理方式。
圖片懶加載的實現(xiàn)方式
? 圖片懶加載的實現(xiàn)實際上用了節(jié)流器來完成,通過設置延遲器,避免在第一時間加載圖片,等待一個指定的時間后加載圖片,達到圖片懶加載的實現(xiàn)。具體的防抖和節(jié)流的實現(xiàn),這里就先不贅述了,等到之后出一片博文之后,來在這里添加一條鏈接。
? 同時這里的圖片懶加載也是基于Scroll的事件監(jiān)聽進行實現(xiàn),然后加入throttle之后,設置等待時間為200ms,來實現(xiàn)show的延遲加載。
? 具體實現(xiàn)代碼如下:
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; //這里來設置節(jié)流,等待時間為200ms,這樣就不會實時出發(fā)scroll滾動事件。 this._lazyLoadHandler = throttle(200, this.handleLazyLoad); on(_scrollContainer, 'scroll', this._lazyLoadHandler); this.handleLazyLoad(); } }
? 實現(xiàn)還是比較簡單的,但是這里的很多東西感覺很值得學習,無論是節(jié)流還是滾動事件的設置。作為現(xiàn)在的新手的我來說是值得學習的,又可以學習使用節(jié)流的場景,也可以學習節(jié)流與事件合并使用的技巧。
圖片加載失敗和加載中的可配置的實現(xiàn)
這里的圖片加載失敗和加載中的可配置實現(xiàn),其實都是基于slot進行實現(xiàn)的,也就是vue中插槽,并且這里使用了具名插槽的使用。插槽的使用還是比較簡單的,這里就放一下vue官網(wǎng)對插槽的介紹:vue插槽
圖片預覽的功能模塊實現(xiàn)(image-view.vue文件)
先放一張圖,通過這個圖里的內容來介紹預覽功能
? 圖片預覽模塊的功能實現(xiàn)就比較好玩了,主要分為上面說的幾個方面,現(xiàn)在就來說一下這些方面:
布局:
頁面采用的布局實際就是蒙版的實現(xiàn),但是
image-view
的蒙板實現(xiàn)其實存在一些問題,就是當把組件插在頁面中的時候,如果整個頁面是很長的頁面,那么在對圖片使用滾輪縮放放大的時候,主頁面也會被影響。整體的頁面布局采用的還是flex
布局的形式,同時將justify-content
設置問center
,align-item
也設置為center
,這樣就可以保證image
的居中顯示了。其他的圖標都為absolute
,圖標組依然在absolute
的基礎上,繼續(xù)設置了flex
布局,同時為了保證每一個圖標在圖標組內均為分布,justify-content
設置為了space-around
。flex布局牛逼,節(jié)省了好多時間,只需要調解基線就完事了。
放縮
圖片放縮的功能是基于css中
transform
屬性,transform
屬性提供了scale
的調整,所以很方便就實現(xiàn)了圖片的放縮,同時在圖片使用transform
的時候,會被默認提升到最頂層,這就完美的解決了蒙版遮蓋的問題,一舉兩得?;A的放縮在image組件的控制是基于兩個放縮按鈕來實現(xiàn)的,所以也是很方便的。旋轉
圖片旋轉的功能是基于css中
transform
屬性,transform
屬性提供了rotate
的調整,所以很方便就實現(xiàn)了圖片的旋轉。在旋轉與放縮的實現(xiàn)中,會設置一個判斷是否是通過普通按鈕操作的判斷,如果是普通的操作就會再加上一個
transition
來設置切換的時間,讓交互更加友好。但是在滾輪的縮放中,就需要取消transition
屬性,因為滾輪的滑動十分迅速,交互十分頻繁,為了達到節(jié)流的要求,就需要設置滾輪每一次滾動的步長比普通按鈕的長,且取消transition
,防止直接卡死。等比例調整
等比例調整功能實現(xiàn)也很簡單,只需要原本設置的
max-width
與max-height
直接取消掉就可以實現(xiàn)了。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屬性,這樣就不需要可以修改這兩個屬性,一旦發(fā)生改變就可以觸發(fā) computed: { currentImg() { return this.urlList[this.index]; }, viewerZIndex() { const nextZIndex = PopupManager.nextZIndex(); return this.zIndex > nextZIndex ? this.zIndex : nextZIndex; } } //具體的實現(xiàn) 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來優(yōu)化鼠標滾輪滑動的效率,讓其事件的觸發(fā)與瀏覽器刷新頻率相同,優(yōu)化性能。
對于鼠標滾輪的事件,在不同的瀏覽器中擁有不同的事件相應名稱,在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開發(fā)的image組件中,我才用了上述的寫法, 并做了測試,效果還不錯啦
這里讓我主要學習到的是鼠標滾輪事件的優(yōu)化,就是上面說的結合window.requestAnimationFrame來使用的方法,真的感覺很棒,這樣每一次滾輪事件的觸發(fā),都結合了當前屏幕的實時刷新率,讓其效果達到最好。
具體的代碼實現(xiàn)貼出:
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; }); }; }
基于鍵盤輸入的縮放
基于鍵盤的縮放就是對鍵盤輸入做了監(jiān)聽,并且對上下左右四個方向鍵以及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鍵,一種是點擊蒙版而不是圖片的位置,最后一種就是點擊按鈕,其實現(xiàn)都是完全相同,這里我有點不明白為什么要把方法作為prop傳過來,然后再執(zhí)行,為什么不可以做為
emit
來處理。實現(xiàn)還是很簡單,就不貼了。基于鼠標按下事件的拖放功能
鼠標按下事件的拖放功能主要依賴于vue中支持的mousedown事件來實現(xiàn),同時在mousedown的同時,綁定上mousemove事件,來獲取移動的x,y軸的位置,給到transform上的offsetX與offsetY屬性,用來設置imageStyle的margin-left,margin-top屬性。其實還是比較簡單的。
具體代碼實現(xiàn)如下:
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); }); //當事件處理結束后,就會將本事件取消,不會進一步發(fā)生 e.preventDefault(); }
關于elementui管理事件的一些解讀
elementui的事件管理都是通過一個柯里化的方法來進行的,就是src/utils/dom.js下的on和off方法,這兩個方法寫的十分巧妙,可以通過判斷是不是服務端渲染的代碼,返回不同的事件綁定的代碼,具體實現(xiàn)如下:
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) } } } }
雖然這對綁定事件來說會變得十分簡單,但是還是有一個關鍵的問題就是我無法知道我當前事件是否被消除,所以很想知道有沒有很好的事件管理機制來負責處理這些被創(chuàng)建的事件,然后再不需要在使用這些的事件時候,去處理掉這些事件,這也是之后需要我去探索的內容。
總結
element-ui的image組件的源碼閱讀是我第一個仔細分析的它運行的每一步的一個組件,之前雖然也看過了兩個比較復雜模塊的源碼 table
以及 tree
的實現(xiàn),但是卻沒有這次這么仔細地去分析他的每一個步驟以及頁面的布局設計。在本次閱讀的過程中,學到了怎么去優(yōu)化頻繁交互的處理方法,以及防抖和節(jié)流的使用
到此這篇關于elementui下image組件的使用的文章就介紹到這了,更多相關element image組件內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Vue實現(xiàn)手機號、驗證碼登錄(60s禁用倒計時)
這篇文章主要介紹了Vue實現(xiàn)手機號、驗證碼登錄(60s禁用倒計時),幫助大家更好的理解和使用vue,感興趣的朋友可以了解下2020-12-12vue3?elmentPlus?table實現(xiàn)列寬可拖拽功能
這篇文章主要介紹了vue3?elmentPlus?table實現(xiàn)列寬可拖拽功能,本文通過示例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-08-08