iview在ie9及以上的相容問題解決方案

膽小咯君發表於2018-09-09

是時候亮出這張圖了:

iview在ie9及以上的相容問題解決方案

可是ie不是你不想相容就不相容啊。說多了都是淚。

使用iview已經有一年多的時間。總的來說,iview還是給我的工作帶來了很大的方便。

主要的吐槽點就是文件寫的不夠清楚。

比如元件的按需引入,寥寥數語,看完按照文件引入了,結果控制檯一直報錯。

iview在ie9及以上的相容問題解決方案

然後到隔壁element一看,原來引入方式並不是只有一種,有的需要通過vue.prototype.xxx這種方式。

還有對於相容性的描述,

iview在ie9及以上的相容問題解決方案

你這麼一說我還以為ie9直接引入polyfill就可以用了呢,誰知道根本不行。在github上提issue又說不支援低版本ie了。

根據我的經驗,不需要很大改動,相容性大概ie10+,這也是element官方文件上的相容性。

不知道iview這麼做是為了吸引更多人入坑還是怎麼回事,畢竟大多數開發者開發的時候都是先看官方文件而不是先去github找issue。

吐槽歸吐槽,iview總體上還是不錯的。下面說一下我在使用iview的過程中所遇到的ie9+的相容性問題及解決方案。

安裝babel-polyfill

IE瀏覽器沒有內建Promise物件。不僅如此,幾乎所有的ES6新增的方法在IE都不能用,此時你需要babel Polyfill

  1. 首先

    npm install babel-polyfill --save
    複製程式碼
  2. 修改webpack.base.conf.js

    修改前

    entry: {
        main: './src/main',
    },
    複製程式碼

    修改後

    entry: {
        main: ["babel-polyfill","./src/main"],
    },
    複製程式碼

    看到網上有的教程安裝完babel-polyfill又要安裝es6-prommise,只能說一句:畫蛇添足。

相容dataset

[Vue warn]: Error in directive transfer-dom inserted hook: "TypeError: 無法獲取未定義或 null 引用的屬性“transfer”"

這是ie10及以下不支援dataset導致的,而iview的transfer-dom.js使用了這個屬性

解決辦法:在main.js加入如下程式碼

if (window.HTMLElement) {
    if (Object.getOwnPropertyNames(HTMLElement.prototype).indexOf('dataset') === -1) {
        Object.defineProperty(HTMLElement.prototype, 'dataset', {
            get: function () {
                var attributes = this.attributes; // 獲取節點的所有屬性
                var name = [];
                var value = []; // 定義兩個陣列儲存屬性名和屬性值
                var obj = {}; // 定義一個空物件
                for (var i = 0; i < attributes.length; i++) { // 遍歷節點的所有屬性
                    if (attributes[i].nodeName.slice(0, 5) === 'data-') { // 如果屬性名的前面5個字元符合"data-"
                        // 取出屬性名的"data-"的後面的字串放入name陣列中
                        name.push(attributes[i].nodeName.slice(5));
                        // 取出對應的屬性值放入value陣列中
                        value.push(attributes[i].nodeValue);
                    }
                }
                for (var j = 0; j < name.length; j++) { // 遍歷name和value陣列
                    obj[name[j]] = value[j]; // 將屬性名和屬性值儲存到obj中
                }
                return obj; // 返回物件
            },
        });
    }
}

複製程式碼

降級依賴版本

如果遇到以下錯誤:

錯誤1:“webpackJsonp”未定義

解決方案:

更改webpack-dev-server版本為2.71或更低

npm install --save-dev webpack-dev-server@2.7.1
複製程式碼

相容requestAnimationFrame(ie9)

ie9是不支援requestAnimationFrame的,如果你使用了出現錯誤,那也沒關係,往下看就行了。

解決方案:新增以下程式碼到main.js

// window.requestAnimationFrame多瀏覽器相容問題補丁
// http://paulirish.com/2011/requestanimationframe-for-smart-animating/
// http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
// requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel
// MIT license

(function () {
    var lastTime = 0;
    var vendors = ['ms', 'moz', 'webkit', 'o'];
    for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
        window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
        window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] ||
            window[vendors[x] + 'CancelRequestAnimationFrame'];
    }

    if (!window.requestAnimationFrame) {
        window.requestAnimationFrame = function (callback, element) {
            var currTime = new Date().getTime();
            var timeToCall = Math.max(0, 16 - (currTime - lastTime));
            var id = window.setTimeout(function () { callback(currTime + timeToCall); },
                timeToCall);
            lastTime = currTime + timeToCall;
            return id;
        };
    }

    if (!window.cancelAnimationFrame) {
        window.cancelAnimationFrame = function (id) {
            clearTimeout(id);
        };
    }
}());

複製程式碼

相容classList(ie9)

錯誤資訊: 無法獲取未定義或 null 引用的屬性“add”

無法獲取未定義或 null 引用的屬性“remove”

如果你檢視sourceMap發現了classList().add或classList.remove()等等,那肯定是classList的問題了。

解決方案:新增以下程式碼到main.js

if (!('classList' in document.documentElement)) {
    Object.defineProperty(HTMLElement.prototype, 'classList', {
        get: function () {
            var self = this;
            function update(fn) {
                return function (value) {
                    var classes = self.className.split(/\s+/g);
                    var index = classes.indexOf(value);

                    fn(classes, index, value);
                    self.className = classes.join(' ');
                };
            }

            return {
                add: update(function (classes, index, value) {
                    if (!~index) classes.push(value);
                }),

                remove: update(function (classes, index) {
                    if (~index) classes.splice(index, 1);
                }),

                toggle: update(function (classes, index, value) {
                    if (~index) { classes.splice(index, 1); } else { classes.push(value); }
                }),

                contains: function (value) {
                    return !!~self.className.split(/\s+/g).indexOf(value);
                },

                item: function (i) {
                    return self.className.split(/\s+/g)[i] || null;
                },
            };
        },
    });
}
複製程式碼

專案內路由跳轉避免使用location.href

為了更好的效能,我們通常會採用路由懶載入。在單頁面應用中,每開啟一個頁面基本上都只載入對應的資原始檔。

如果跳轉到之前的頁面,通常頁面會採用快取的資原始檔,不會再次載入,這樣有效提高了頁面載入效率。

而如果採用location.href這種方式,頁面會全部重新整理,重新下載所有頁面資源,不能很好的利用快取。

這些可以從chrome控制檯的network去檢視。

更重要的是,如果直接採用location.href,ie瀏覽器可能會出現url變化頁面不重新整理的情況!

比如路由如下:

127.0.0.1:8080/#/home

127.0.0.1:8080/#/about

當前頁面是127.0.0.1:8080/#/home,

點選按鈕跳轉

jump(){
    location.href = '/#/about'
}
複製程式碼

你會發現,瀏覽器位址列url變了,然後頁面還是home頁面! 所以你應該這樣用

jump(){
    let url = '/#/about';
    let path = url.split('#')[1];
    this.$router.push(path);
}
複製程式碼

這下跳轉就沒問題了。

當然,如果你有一些地方必須要使用location.href,比如你接入了第三方的一些服務,你無法控制,也有解決方法:在vue根例項的created或者mounted生命週期新增如下程式碼:

window.addEventListener('hashchange', () => {
    let currentPath = window.location.hash.slice(1);
    if (this.$route.fullPath !== currentPath) {
        this.$router.push(currentPath);
    }
}, false);
複製程式碼

通過監聽hashchange,一旦發現當前頁面url與瀏覽器位址列url不同,就呼叫vue的路由方法跳轉到位址列url去。

如果目的頁面被keep-alive也會觸發這個方法,但是沒有影響。

使用scrollTop

如果頁面太長,我們會加個滾動到頂部的按鈕

toTop(el){
    el.scrollTo(0, 0);
}

複製程式碼

誰知道ie下竟然無動於衷!嘗試了一番,才發現是ie瀏覽器不支援scrollTo所致。

改成這樣:


toTop(el){
    if (el && el.scrollTo) {
        el.scrollTo(0, 0);
    } else {
        el.scrollTop = 0;
    }
}

複製程式碼

ie也可以滾動到頂部了。

ie9不支援cors跨域

這點是最坑的,頁面在ie10+開啟雖然有些地方也會報錯,但是ie9完全不會展示頁面,也就是說一個頁面都展示不出來!

控制檯報錯:訪問拒絕!

除此之外再無其他資訊。

反覆檢視axios和vue的文件,都說支援ie9.

這問題折磨了我好久,沒有具體錯誤資訊,根本無從下手。

一個一個排查,不會是axios的問題吧。

一查發現果然是:ie8/9不支援cors跨域方案,取而代之的是ie的XDomainRequest方法

不想再去研究XDomainRequest方法了。直接用webpack-dev-server提供的伺服器代理方法(前提是後臺已經配置好跨域),大概這樣:

devServer: {
    port: 8080,
    proxy: {
        '/api': {
        target: 'http://xx.xx.cn/',
        pathRewrite: {'^/api' : ''},
        changeOrigin: true
        }
    }
}

複製程式碼

想要了解更多的可以看看這個IE8、9 下的資源跨域請求

日期格式化不要使用new Date(yy-mm-dd)

在ie瀏覽器下,如果直接使用例如new Date('2018-09-12'),ie會顯示Invalid Date,為了保證在所有瀏覽器表現一致,應該採用new Date('2018/09/12')這種方式。

判斷瀏覽器版本

function IEVersion () {
	var userAgent = navigator.userAgent; // 取得瀏覽器的userAgent字串
	var isIE = userAgent.indexOf('compatible') > -1 && userAgent.indexOf('MSIE') > -1; // 判斷是否IE<11瀏覽器
	var isEdge = userAgent.indexOf('Edge') > -1 && !isIE; // 判斷是否IE的Edge瀏覽器
	var isIE11 = userAgent.indexOf('Trident') > -1 && userAgent.indexOf('rv:11.0') > -1;
	if (isIE) {
		var reIE = new RegExp('MSIE (\\d+\\.\\d+);');
		reIE.test(userAgent);
		var fIEVersion = parseFloat(RegExp['$1']);
		if (fIEVersion === 7) {
			return 7;
		} else if (fIEVersion === 8) {
			return 8;
		} else if (fIEVersion === 9) {
			return 9;
		} else if (fIEVersion === 10) {
			return 10;
		} else {
			return 6;// IE版本<=7
		}
	} else if (isEdge) {
		return 'edge';// edge
	} else if (isIE11) {
		return 11; // IE11
	} else {
		return -1;// 不是ie瀏覽器
	}
};
複製程式碼

一些新特性不要用

用了translateX()發現ie9無動於衷,還是乖乖用相對定位吧;

線性漸變linear-gradient()可以用,但是ie不會識別的,所以先設定一個純色背景或圖片背景再設定線性漸變吧,不然ie背景設定不上。

flex佈局是挺爽,但是別用。想要同樣的效果,table佈局也不錯。

相關文章