<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Vue 可視化大屏適配插件之過(guò)程篇

          共 6709字,需瀏覽 14分鐘

           ·

          2022-09-29 15:51

          一直以來(lái)都想自己寫一款插件去解決大屏的適配問(wèn)題,最近終于有時(shí)間去完成這件事,特此記錄下過(guò)程中碰到的問(wèn)題。

          注冊(cè) vue 指令如何支持類型提示?

          文檔說(shuō) vue 插件的 use方法是支持第二個(gè)參數(shù)的,一開(kāi)始打算通過(guò)第二個(gè)參數(shù)做基礎(chǔ)配置。能正確讀取到該參數(shù),可是不知道怎么做類型提示,因?yàn)楣俜蕉x的是 any[], 那我總不能讓使用者去從我的插件里導(dǎo)出類型再去 as 吧?

          谷歌了問(wèn)題,翻了 issue , 也找了一些開(kāi)源的插件去看,好像大家都沒(méi)這個(gè)需求。

          開(kāi)端就遇到問(wèn)題,搞得我都不是很有動(dòng)力寫下去了。

          后來(lái)下班路上在地鐵里猛然想起來(lái)第一個(gè)參數(shù)可以是 objectfunction, 那 function 不是支持傳參嗎?就這樣第一個(gè)問(wèn)題解決掉了。

                import?Fit?from?'vue-fit-next'
          app
          ??.use(
          ????Fit({??//?這里就有了類型提示了
          ??????width:?3840,
          ??????height:?2160,
          ????})
          ??)
          ??.mount('#app')
          復(fù)制代碼

          插件叫什么名字?

          如果不能為你的插件提供一個(gè)意義明確且好記的名字,那么這個(gè)插件很可能不會(huì)讓人有特別想使用的欲望。

          類似 adapter-screen 這種估計(jì)已經(jīng)有人使用了,而且名字太長(zhǎng)也不好拼。

          后來(lái)想到了一個(gè)單詞fit, 因?yàn)檫@個(gè)在寫 css的時(shí)候會(huì)用到,比如object-fit:cover, 查了下翻譯軟件確實(shí)有適配、合身的意思。

          vue-fit 吧, 但是到 npm一搜,發(fā)現(xiàn)幾年前就被人占用了。

          那不如就叫vue-fit-next 吧。

          如何適配?

          整體縮放

          核心可能還是 scale, 剛開(kāi)始采用的網(wǎng)頁(yè)整體 scale。

          基本思路就是用innerWidth和設(shè)計(jì)稿寬度計(jì)算比值,然后高度和寬度中采用比值最小的一個(gè)。

                ??const?w?=?window.innerWidth?/?defaultFitOptions.width
          ??const?h?=?window.innerHeight?/?defaultFitOptions.height
          ??const?scale?=?Math.min(w,?h)?//?寬度與高度的比例取最小的,以確保屏幕可以完全顯示
          復(fù)制代碼

          這樣能達(dá)到基本效果,但是會(huì)帶來(lái)新的問(wèn)題:

          • 如果不使用F11(全屏模式),右邊和下邊會(huì)出現(xiàn)”留白“。
          • 不能適配非設(shè)計(jì)稿之外的分辨率。

          分塊縮放

          把網(wǎng)頁(yè)分成很多小塊,分別對(duì)這些小塊進(jìn)行縮放,就能解決”留白“問(wèn)題了。

          比如把一張網(wǎng)頁(yè)分成九塊,再配合位置調(diào)整。那么”留白“就留到了小塊的”邊界處“,在視覺(jué)上就看不出”留白“了。

                ?????-----------------------------------------
          ????|????????????|??????????????|?????????????|
          ????|????left????|????center????|????right????|
          ????|????????????|??????????????|?????????????|
          ?????-----------------------------------------
          ????|????????????|??????????????|?????????????|
          ????|?leftCenter?|?centerCenter?|?rightCenter?|
          ????|????????????|??????????????|?????????????|
          ?????-----------------------------------------
          ????|????????????|??????????????|?????????????|
          ????|?leftBottom?|?centerBottom?|?rightBottom?|
          ????|????????????|???????????????|????????????|
          ?????-----------------------------------------
          復(fù)制代碼

          transform變換中心問(wèn)題

          如上所示,需要把每個(gè) div 分隔開(kāi),就會(huì)涉及到縮放之后的對(duì)齊問(wèn)題,也就是 transform-origin的值。

          默認(rèn)是從元素的正中心進(jìn)行縮放的,我們可以進(jìn)行設(shè)置。

          但是不管我們?cè)趺丛O(shè)置 transform-origin 的值, 在屏幕不同位置的元素都有可能出現(xiàn)位置不正確的情況。

          比如,設(shè)置 transform-origin: left top之后左上角的元素位置正確了。但是右上角的卻錯(cuò)了,右邊和下邊會(huì)出現(xiàn)”留白“。

          因此需要針對(duì)不同位置的元素設(shè)置不同的transform-origin值再加上簡(jiǎn)單的偏移計(jì)算。

          這就是為什么需要用戶在寫指令的時(shí)候定義好一個(gè) ”區(qū)域“ 。

          如何計(jì)算偏移量

          也許你用過(guò) offsetTop 或者 offsetLeft, 但應(yīng)該沒(méi)用過(guò)offsetRightoffsetBottom吧?

          是的,當(dāng)我需要他們兩的時(shí)候才發(fā)現(xiàn)根本沒(méi)這兩屬性。

          比如當(dāng)元素靠近右側(cè)時(shí),我需要去計(jì)算物體的offsetRight, 因?yàn)橛脩艉苡锌赡芡ㄟ^(guò)CSS設(shè)置了 rightmargin-right等位置屬性,

          而這些用戶手動(dòng)設(shè)置的屬性是沒(méi)有參與scale的, 就會(huì)導(dǎo)致位置不準(zhǔn)確。那我需要通過(guò)代碼進(jìn)行”糾偏“。

          通過(guò)getComputedStyle這個(gè)方法去獲取元素的css屬性值,就得到了右偏移和下偏移的值,再把這個(gè)值通過(guò)translate的方式偏移。

                /**?獲取css?屬性值?*/
          export?function?getComputedStyleNumber(el:?HTMLElement,?attr:?string)?{
          ??return?parseFloat(getComputedStyle(el,?null).getPropertyValue(attr)?||?'0')
          }
          const?offsetRight?=?(getComputedStyleNumber(el,?'right')?+?getComputedStyleNumber(el,?'margin-right'))
          const?offsetBottom?=?(getComputedStyleNumber(el,?'bottom')?+?getComputedStyleNumber(el,?'margin-bottom'))
          復(fù)制代碼

          動(dòng)畫帶來(lái)的問(wèn)題

          動(dòng)畫搶占 transform

          插件本身還具備入場(chǎng)和出場(chǎng)動(dòng)畫功能,最開(kāi)始是打算直接用animation.css這個(gè)庫(kù)的。

          • 可以保證插件具有很多可選的動(dòng)畫效果。
          • 使用簡(jiǎn)單,用戶只需要添加對(duì)應(yīng)的 class屬性就行了,沒(méi)有額外的學(xué)習(xí)成本。

          但在結(jié)合的過(guò)程中卻出現(xiàn)了問(wèn)題,animation.css也使用了transform,就會(huì)導(dǎo)致 css 沖突覆蓋。

          animate的動(dòng)畫結(jié)束之后就把插件的scale值覆蓋成默認(rèn)值了,這就變得麻煩起來(lái)了。

          也想過(guò)做一些迂回:比如加一層 dom專門做動(dòng)畫。但這樣的方式不是我想要的, 會(huì)破環(huán)用戶原始的dom結(jié)構(gòu),那還不如不要?jiǎng)赢嫷墓δ芰恕?/p>

          那干脆放棄animate.css吧,自己用js做動(dòng)畫,在位置變換的同時(shí)設(shè)置好scale就能解決這個(gè)問(wèn)題了。

          transform 順序問(wèn)題

          在做動(dòng)畫的過(guò)程中碰到了另一個(gè)問(wèn)題,關(guān)于書寫順序的。搞得我?guī)锥葢岩扇松?,以為是自己?translate值計(jì)算錯(cuò)了...

                /*?錯(cuò)誤?*/
          transform:?scale(${scale},?${scale})?translate3d(-100%,?${y}px,?0);
          /*?正確?*/
          transform:?translate3d(-100%,?${y}px,?0)?scale(${scale},?${scale});
          復(fù)制代碼

          盡管我還是無(wú)法理解先寫scale和先translate的區(qū)別,但是他符合預(yù)期了。

          如何動(dòng)態(tài)更新動(dòng)畫

          一開(kāi)始計(jì)算出scale值之后就能生成 keyframes的代碼了,但是如果用戶縮放瀏覽器之后呢,那我也應(yīng)該更新下keyframes的代碼。就用到了CSSStyleSheet這個(gè),他提供了insertRuledeleteRule兩個(gè)方法,幫助我們插入和刪除樣式。

                styleSheet?.insertRule(
          ??`@keyframes?slideInLeft_${nanoId}?{
          ??????from?{
          ????????transform:?translate3d(-100%,?${y}px,?0)?scale(${scale},?${scale});
          ??????visibility:?visible;
          ????}
          ????to?{
          ??????transform:?translate3d(${x}px,?${y}px,?0)?scale(${scale},?${scale});
          ????}
          ??}`

          )
          復(fù)制代碼

          通過(guò) styleSheet?.deleteRule(index)傳入樣式規(guī)則的下標(biāo)即可刪除樣式。

                ?const?rules?=?styleSheet?.cssRules
          ??if?(rules)?{
          ????while?(rules.length)
          ??????styleSheet?.deleteRule(0)
          ??}
          復(fù)制代碼

          不得不用的 Transition 組件

          這樣就添加好入場(chǎng)動(dòng)畫了,沒(méi)想到的是出場(chǎng)動(dòng)畫出了岔子。

          我并不知道vue會(huì)在什么時(shí)候刪除元素,當(dāng)用戶通過(guò)v-if或者:is去管理組件的時(shí)候,元素會(huì)被立即銷毀,出場(chǎng)動(dòng)畫也就沒(méi)機(jī)會(huì)動(dòng)畫了。

          那我也不能去要求用戶把這個(gè)控制元素狀態(tài)的變量傳遞給我吧?那豈不是太神經(jīng)啦,你的組件關(guān)我什么事哦?

          好在vuetransition組件提供了對(duì)應(yīng)的鉤子,只能讓用戶在跟組件包裹下Transition組件了,并且把leave的鉤子傳遞給我的插件。

          講真,這個(gè)要求應(yīng)該不算過(guò)分吧~ 并不會(huì)對(duì)用戶的代碼產(chǎn)生什么破壞性的影響~

          事件

          到這里幾乎就沒(méi)什么大問(wèn)題了,給window添加對(duì)應(yīng)的事件就行, 事件這一塊就靠rxjs來(lái)幫我了。

          拖動(dòng)

          核心是整體拖動(dòng),觸發(fā)對(duì)應(yīng)的事件之后,對(duì)整個(gè)body元素設(shè)置位置更新

                /**
          ?*?按住?space?鍵的同時(shí)鼠標(biāo)點(diǎn)擊界面進(jìn)行拖動(dòng).
          ?*/

          spaceDown$.pipe(
          ??mergeMap(()?=>?mousedown$.pipe(
          ????map((event)?=>?{
          ??????const?{?x,?y?}?=?getTranslateValue(body)
          ??????return?{
          ????????x:?event.clientX?-?x,
          ????????y:?event.clientY?-?y,
          ??????}
          ????}),
          ????mergeMap(({?x,?y?})?=>?mousemove$.pipe(
          ??????map(ev?=>?({
          ????????x:?ev.clientX?-?x,
          ????????y:?ev.clientY?-?y,
          ??????})),
          ??????takeUntil(mouseup$),
          ????)),
          ????throttleTime(20),
          ????takeUntil(
          ??????spaceUp$.pipe(
          ????????tap(()?=>?body.style.cursor?=?cursor),
          ??????)),
          ??)),
          ).subscribe((value)?=>?{
          ??bodyTransform$.next(setBodyTransform(value))
          })
          復(fù)制代碼

          縮放

                /**
          ?*??按住?ctrl?鍵的同時(shí)滾動(dòng)鼠標(biāo),實(shí)現(xiàn)整體縮放及位置偏移.
          ?*/

          const?ctrlMousewheel$?=?(seed:?Required<TransformType>)?=>?mousewheel$.pipe(
          ??filter(isCtrlKey),
          ??tap(event?=>?event.preventDefault()),
          ??scan(calcTransform,?seed),
          )
          復(fù)制代碼

          復(fù)原

                /**
          ?*?雙擊?space?鍵進(jìn)行位置復(fù)原.
          ?*/

          spaceDown$.pipe(
          ??bufferWhen(()?=>?spaceDown$.pipe(debounceTime(250))),
          ??filter(list?=>?list.length?===?2),
          ).subscribe(()?=>?{
          ??bodyTransform$.next(setBodyTransform({?x:?0,?y:?0,?scale:?1?}))
          })
          復(fù)制代碼

          和3D交互的沖突

          當(dāng)我們使用鼠標(biāo)進(jìn)行拖動(dòng)或者縮放時(shí),這些操作在對(duì)應(yīng)的3d場(chǎng)景中也會(huì)被觸發(fā),就造成了沖突,因此在出發(fā)我們的事件時(shí)需要隔離3D事件。最簡(jiǎn)單的方式就是創(chuàng)建一個(gè)mask放到最外邊。

                /**
          ?*?進(jìn)行拖動(dòng)或者放大時(shí)添加遮罩以免影響3D場(chǎng)景
          ?*/

          merge(
          ??spaceDown$,
          ??mousewheel$.pipe(filter(isCtrlKey)),
          ).pipe(throttleTime(500))
          ??.subscribe(()?=>?{
          ????document.body.appendChild(fitMask)
          ??})

          /**
          ?*?鍵盤抬起時(shí)移除遮罩
          ?*/

          keyup$.subscribe(()?=>?{
          ??document.querySelector('#fitMask')?.remove()
          })
          復(fù)制代碼

          源碼

          至此,這個(gè)插件就算完成了,沒(méi)想到這么小小的一個(gè)功能,卻碰到了辣么多的問(wèn)題。

          希望這個(gè)插件能好用吧~

          最后,源碼和示例都在 github 上了, 求大佬們來(lái)點(diǎn) star。

          • github [1]

          • 同事說(shuō)大屏適配方案能用就行,我偏不! [2]

          關(guān)于本文

          作者:瞌睡睡不完

          https://juejin.cn/post/7134985068786745374

          瀏覽 65
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  黄色视频1级毛片 | 精品人妻系列 | 级毛片内射视频 | 国产一卡二卡在线播放 | 亚洲欧美日韩中文在线 |