博客 / 詳情

返回

微前端下element-ui等前端UI框架彈框偏移問題解決

本章1~6主要是解決無界微前端環境下element-ui彈框偏移問題,如果你用的是其他微前端框架,且提供了jsloader這種預處理器,則可以舉一反三解決同樣的問題。

7後面的內容可以通用react、vue、anglar,大家都可以嘗試嘗試,建議先用插件形式試試看,因為插件可以解決所有UI框架的偏移問題

如果不想看我廢話,請直接移步到5看代碼和後面的效果圖【此方法已經廢棄,因為官網更新了源碼。現在直接看6.新方法測試可以不偏移】。

1. 首先,我使用的是無界官方源碼,下載地址:無界微前端源碼

如圖已經下載到本地了:使用pnpm i安裝一下依賴
image.png
如果報錯,請更新你的nvm或者使用16.19.0版本的node

2. 啓動官網例子:npm run start,正確啓動的話可以看到一下頁面:

image.png
點擊進入vue2的dialog頁面。

3. 我們打開examples\vue2\src\main.js,在頂部任意地方加入:

import Row from "element-ui/lib/row";
import Col from "element-ui/lib/col";
import "element-ui/lib/theme-chalk/row.css";
import "element-ui/lib/theme-chalk/col.css";
[Row, Col].forEach((element) => Vue.use(element));

如圖:
image.png

4. 打開examples\vue2\src\views\Dialog.vue,寫入代碼:

<template>
<a-button @click="fullDialogVisible = true" style="margin-left: 20px">點擊打開全屏彈窗</a-button>

 <el-dialog title="全屏彈窗" fullscreen :visible.sync="fullDialogVisible" width="30%">
      <el-row type="flex" justify="space-between">
        <el-col :span="6"
          ><div class="grid-left">
            <el-select v-model="value" placeholder="el-select">
              <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value"></el-option>
            </el-select></div
        ></el-col>
        <el-col :span="6"
          ><div class="grid-center">
            <el-select v-model="value" placeholder="el-select">
              <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value"></el-option>
            </el-select></div
        ></el-col>
        <el-col :span="6"
          ><div class="grid-right">
            <el-select v-model="value" placeholder="el-select">
              <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value"></el-option>
            </el-select></div
        ></el-col>
      </el-row>
      <span slot="footer" class="dialog-footer">
        <el-button @click="fullDialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="fullDialogVisible = false">確 定</el-button>
      </span>
    </el-dialog>
</template>
<script>
...
data() {
    return {
      fullDialogVisible: false
    }
}
...
</script>

以上代碼就是為了寫一個彈框,且彈框內有左中右三個下拉框,來顯示下拉框是否位置正常。

5. 【全文重點】 打開examples\main-vue\src\views\Vue2-sub.vue此文件,寫入:

<template>
<WujieVue width="100%" height="100%" name="vue2" :url="vue2Url" :plugins="plugins"></WujieVue>
</template>
<script>
...
data() {
    return {
      plugins: [
        {
          // 在子應用所有的css之前
          cssBeforeLoaders: [
            // 強制使子應用body定位是relative
            { content: "body{position: relative !important}" },
          ],
        },
        {
          jsLoader: (code) => {
            // 替換popper.js內計算偏左側偏移量
            var codes = code.replace(
              "left: elementRect.left - parentRect.left",
              "left: fixed ? elementRect.left : elementRect.left - parentRect.left"
            );
            // 替換popper.js內右側偏移量
            return codes.replace("popper.right > data.boundaries.right", "false");
          },
        },
      ],
    }
}
...
</script>

按以上操作則可以實現官網例子內的彈框不在偏移。且不論下拉框是何種定位都能實現完美位置。

綜上所述:

你只需更改主應用的plugins即可修復彈框偏移問題;按照5所述,修改即可。(費了大量的時間和精力,一直在尋找一個完美且傻瓜式的解決辦法,最終還是調試源碼,找到此辦法。github上解決此問題的人都是各種奇淫技巧,但我們只需要最樸素且簡單見效的辦法。)

6. 上面提到的方法跟element-ui版本有關,可以嘗試一下。如果不行可以採用下面方法試試(只寫了改源碼。):

解決思路:把popper的dom直接塞進主應用的body中,判斷定位和顯示邊界也直接以主應用作為左邊起點,這樣的效果就和正常在項目中使用appendToBody的效果一致,代碼改動也非常的少。

因為公司內部使用的組件庫是基於element-ui進行封裝的,所以直接在源碼上進行更改了。
如果不能直接修改element-ui的源碼,明確修改代碼位置後,可以使用js-loader或者項目build之前修改node_modules代碼。
以下代碼目錄都是基於element-ui源碼的目錄,對應在node_modules中的地址是lib/utils。

找到文件:node_modules/element-ui/src/utils/vue-popper.js
// 源碼
this.appendToBody && document.body.appendChild(this.popperElm);
// 修改後
if (this.appendToBody) {
    if (window.__POWERED_BY_WUJIE__) {
      window.parent.document.body.appendChild(this.popperElm);
    } else {
      document.body.appendChild(this.popperElm);
    }
  }

找到文件:node_modules/element-ui/src/utils/popper.js
// 源碼
var root =  window;
// 修改後
var root = window.__POWERED_BY_WUJIE__ ? window.parent : window;
// 源碼
function getStyleComputedProperty(element, property) {
      // NOTE: 1 DOM access here
      var css = root.getComputedStyle(element, null);
      return css[property];
  }
// 修改後
function getStyleComputedProperty(element, property) {
      // NOTE: 1 DOM access here
      // wujie環境下向上遍歷offsetParent時 過濾document類型得節點,避免方法getComputedStyle報錯
      if (window.__POWERED_BY_WUJIE__ && element.nodeType === 9) return 'static'; 
      var css = root.getComputedStyle(element, null);
      return css[property];
  }

jsloader方式

    {
          jsLoader(code) {
            // 解決element-ui,popper.js計算問題
            let newCode = code.replace('var root = window;','var root = window.parent')
            .replace('document.body.appendChild(this.popperElm);','window.parent.document.body.appendChild(this.popperElm);')
            .replace("var css = root.getComputedStyle(element, null);","if (window.__POWERED_BY_WUJIE__ && element.nodeType === 9) return 'static';var css = root.getComputedStyle(element, null);");
            return newCode;
          }
    }

最終實現效果展示:
GIF.gif

如果替換不成功可以看評論區,把element-ui給排除壓縮混淆,再打包,就不會出現替換不到的情況了。或者你也可以通過patch-package給npm包打補丁解決此問題。

7. 其他解決辦法。

當然了還有其他同學解決問題的辦法。主要是框選內容。這裏貼圖:
5cd7e74da02bc73b774c97d84f4697f7.png

還有甚者通過實現插件形式實現(推薦使用,因為可以解決所有框架偏移問題):

// file:app/wujie/plugins/index.js
export const appendOrInsertElementHook = function (element, iframeWindow) {
  if (element.offsetParent && element.offsetParent.tagName !== 'BODY') {
    return
  }
  const offsetParentDesc = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'offsetParent')
  Object.defineProperties(element, {
    offsetParent: {
      configurable: true,
      get: function () {
        const offsetParent = offsetParentDesc.get.call(this)
        if (
          (offsetParent && offsetParent.tagName !== 'BODY') ||
          element.style.position !== 'fixed'
        ) {
          return offsetParent
        }
        return new Proxy(window.document.documentElement, {
          get: (target, propKey) => {
            if (propKey === 'parentNode') {
              return iframeWindow.document.documentElement.parentNode
            }
            const value = target[propKey]
            const naughtySafari =
              typeof document.all === 'function' && typeof document.all === 'undefined'
            // 只有這些場景下才需要 bind
            if (
              (naughtySafari
                ? typeof value === 'function' && typeof value !== 'undefined'
                : typeof value === 'function') &&
              !(value.name.indexOf('bound ') === 0 && !value.hasOwnProperty('prototype'))
            ) {
              const boundValue = Function.prototype.bind.call(value, target)
              for (const key in value) {
                boundValue[key] = value[key]
              }
              if (value.hasOwnProperty('prototype') && !boundValue.hasOwnProperty('prototype')) {
                Object.defineProperty(boundValue, 'prototype', {
                  value: value.prototype,
                  enumerable: false,
                  writable: true,
                })
              }
              return boundValue
            }
            return value
          },
        })
      },
    },
  })
}

用法:
88f4f061d16dc2a1451a3d17b338a435.png

react的antd框架彈框偏移。請看我這個項目:
https://gitee.com/quqingfei/wujie-react-demo

// App.css增加這行
.ant-pro-layout .ant-pro-layout-content {
  position: initial !important;
}
user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.