H5嵌入原生開發小結----兼容安卓與ios的填坑之路

一開始據說開發H5,覺得就是作適配現代瀏覽器的移動網頁,心想不用管IE了,歐也。到今天,發現當初too young too simple,兼容IE和兼容安卓與IOS,後者讓你更抓狂。接下來數一下踩過的坑。主要分UI展現,鍵盤,輸入框等等。解決bug最苦惱的問題不是沒有解決方案,而是你沒有找到真正的緣由。再就是現象難以重現,每次都要發佈代碼,而後到手機app中去測試,模擬。這些地方會耗費大量的精力。javascript

1、UI相關

1.安卓4.4如下不支持fixed佈局。

fixed佈局的做用之一就是在手機鍵盤彈起來的時候,能夠自動把頁面頂起來。若是不支持的話,換絕對定位也是能夠的。可是絕對定位某些機型好比sm-n7508,華爲m7上仍是沒有能頂起來。IOS沒有這個問題。css

2.小於1px顯示問題

部分安卓機器(好比小米)的分辨率低,若是border的寬度小於1px。安卓機出現一種邊框消失了的現象。樣式上有點奇怪,IOS沒有這個問題。html

一開始覺得是別的元素擋住了,可是調了半天無解。最後忽然意識到是否是計算出來的高度有小數致使的。而後立刻取整:前端

   $target.css("height", Math.ceil(maxline * lineHeight));

可是,華爲的某些類型的是上面顯示不正常,出來一排點點。html5

再修正一下:java

   $target.css("height", Math.ceil(maxline * lineHeight) - 1);

或者用floor。你好奇爲何會有小數高度呢,由於這個功能設計一個摺疊,須要從新計算dom的高度。node

2、鍵盤相關

1.ios鍵盤擋住輸入框。

這個發生的頻率很高,中文輸入法或者輸入法切換的時候會遮擋。ios

解決的辦法以下:程序員

    setInterval(function () { 
if (document.activeElement.className.indexOf('inputcontent') >= 0) { document.activeElement.scrollIntoViewIfNeeded(); } }, 300);

這是最管用的辦法,inputcontent爲輸入框的樣式。activeElement表示得到焦點的元素。可是這個方法只在app中有用,若是是在瀏覽器中仍是會失效。web

2.鍵盤收下留下空白陰影。

 這個在部分安卓機上比較明顯,當鍵盤在激活狀態,忽然來一個模態彈框,很明顯的看到鍵盤收下去以後出現了一個短暫(不到1秒的樣子)的空白,而後頁面再收下去,讓人感受閃了下。好比華爲P7。可是ios上沒有關係,這個問題沒招,系統不流暢。

3.沒法保持鍵盤在彈出狀態。

web其實沒法直接控制鍵盤,只有經過讓輸入框獲focus來讓手機的鍵盤彈出來,可是三星SM-N8507v 這款就是不聽話。我也無解了。

4.確保彈出來的是數字鍵盤

<input type="number" pattern="[0-9]*" />
<input type="password" pattern="[0-9]*" />

只有number或者tel仍是不夠,只有加入正則,ios纔會出現九宮格。

3、輸入框相關

1. fastClick 鎖住輸入框。

在ios中,會出現幾秒的輸入框沒有反應,開始也怎麼想不明白,各類嘗試,推測,搜索發現原來是使用的輕框架中用到了fastClik引發的,解決的辦法就是加上一個樣式。

  <div id="content" class="inputcontent needsclick" ></div>

解決方法到是簡單,可是當初爲找到這個緣由,費了好大勁,把框架的模塊一個一個刪,最後才定位到。若是不加這個,不單會鎖住輸入框,每次調用focus都會設置光標跑在最前面,沒法移動到後面。這個體驗很糟糕。

2.模擬placeholder。

div做爲輸入框,自己加入placeholder是無效的。得藉助於僞元素。

<div id="content" class="inputcontent needsclick" placeholder="有問題就儘管提問吧" contenteditable="true"></div>
.tools .inputcontent:after {
   display: inline-block;
    width: 100%;
    color: #999;
    content: attr(placeholder);
}

而後在得到焦點和失去焦點的時候對這個屬性進行移除和添加操做。這樣作的好處在於,分離用戶輸入的內容,若是把placeholder的值直接在寫輸入框中這樣會多一些判斷,讓代碼不太乾淨。

3.div的換行是div

在div中觸發換行,會獲得一個div。這個就至關因而在編輯器中。而不是咱們認爲的<br>或者換行符。

因此,須要對div進行過濾替換。

     var replace = /<div.*?>(.*?)<\/div>/g;
        txt = txt.replace(replace, function(a) {
         if (a != "<br>") {
                return "<br>" + delHtmlTag(a);
            }
       return delHtmlTag(a);}
       

4.粘貼莫名奇妙帶了樣式

在輸入框中粘貼會以這樣的形式出現:

<font style='font-size:30px;color:#999'><span>粘貼內容</span></font>

不清楚是系統仍是app定義的。因此也得過濾。 用戶還能夠粘貼其餘文章,容易搞亂輸入框中的樣式,須要加上:

.tools .inputcontent *{font-size: 0.50rem !important; color: #000 !important;line-height: 22px !important;font-weight: normal !important;}

由於若是從粘貼事件裏面處理的話,容易搞亂焦點,讓焦點在最前面。這樣很不爽。因此仍是樣式控制,在發送的時候過濾掉全部的標籤。

5.超出遮擋/換行遮擋

這是一個神奇的bug,當內容超過div的最大高度後,最後一行出現一個神奇的現象,頭兩個字顯示了,後面的內容不見了(快快後面其實有內容),直到下一次換行纔會出現。

我alert裏面的內容,發現並無其餘的元素,我不斷的嘗試,發現div overflow: hidden;的時候字都會顯示出來,可是爲了讓用戶可以在內容框裏面上下滾動。我得讓y軸是能夠滾動的:overflow-y: auto; 因此應該是滾動條引發的。但我確實不知道如何修改。後來試出一個hack的方法。只要有一個換行就不會出現這種狀況。因此,我就在用戶輸入到特定行的時候就塞進一個1px的換行:

  if ($("#content").height() == 88 && isIOS() && !haveAppendBr) {
                $("#content").append("<div style='height:1px;border-top:1px solid #fff' ><br></div>");
                haveAppendBr = true;
   }

固然這不是正規的解決辦法,若是園友有遇到類似的劇情,能夠賜教一下。

6.安卓第一次不能換行

這個現象是消息發送成功以後,用戶(小米)一來就是點換行,卻沒法換行,我懷疑是安卓系統阻止了這樣的行爲。可是在輸入一個字以後,換行就是正常的了(哪怕再刪掉這個字)。ios裏面沒有這個問題。開始我嘗試去人爲加一個換行,又發現焦點沒了。想一想這樣問題不改也罷。

7.輸入框光標閃動

這個是translate3d屬性引發,修改sm框架中的一段代碼設置useTranslate爲false。位於handleTouchMove方法中。

4、圖片相關

1.安卓不能上傳。

安卓不少時候都不能觸發input type='file'的彈框,上傳就得走原生的幫助。原生攔截到連接後就會彈出圖片選擇框。

window.location = 'xxapp:h5Upload({"openType":2,"needChop":false,"uploadUrl": '+fileUploadUrl+',"key":' + totalFiles + ',"callbackMethodName": "uploadComplete"})';

key是爲了記住是第幾張圖片,便於在原生上傳結束以後把地址和key一塊兒傳到uploadComplete方法中。這樣界面上才能夠作相應的修改。但這不是我說的重點。重點是Url不要帶中文啊或者特殊符號之類的。一方面不少手機裏面的圖片命名很奇怪,一大堆,像在uc上面保存下來的圖片後面跟了幾個類型。這種名稱沒啥用,很是建議服務器端像微信同樣處理上傳圖片,成功以後獲得一個惟一的字符串就行。否則有的系統會自動解碼你得區分的用上了encodeURI或者encodeURIComponent。測試妹子會說這個手機能夠,那個不能夠,而後是苦了前端。

2.圖片轉了90度。

安卓部分機型(小米2A,三星N7,三星G9)對於拍照的圖片上傳以後竟然左轉了90度。

(非原圖)

我把照片發出來以後,放在桌面上也是歪的,拍攝的圖片自己會帶有一個exifdata,這個對象裏面包含了拍攝的時間、方向、曝光時間等一些信息。用exif.js能夠看出來。也有網站能夠直接查看。

 function getdata(id) {
            EXIF.getData(document.getElementById(id), function () {
                var data = EXIF.getTag(this, 'Orientation');
                console.log(data);
            });
        }

思路就是經過這個方向來判斷是否給圖片來個再旋轉。

 this.style.transform = 'rotate(' + current + 'deg)';

實際沒有使用exif.js,由於exif還須要再請求一次,並且這個圖片有驗證,竟然還讀不到方向信息。最後讓後端對上傳圖片進行方向旋轉糾正。我本身實現了一個。

  public static Bitmap RotateImage(Stream sm)
        {
            Image img = Image.FromStream(sm);
            var exif = img.PropertyItems;
            byte orien = 0;
            var item = exif.Where(m => m.Id == 274).ToArray();
            if (item.Length > 0)
                orien = item[0].Value[0];
            switch (orien)
            {
                case 2:
                    img.RotateFlip(RotateFlipType.RotateNoneFlipX);//horizontal flip  
                    break;
                case 3:
                    img.RotateFlip(RotateFlipType.Rotate180FlipNone);//right-top  
                    break;
                case 4:
                    img.RotateFlip(RotateFlipType.RotateNoneFlipY);//vertical flip  
                    break;
                case 5:
                    img.RotateFlip(RotateFlipType.Rotate90FlipX);
                    break;
                case 6:
                    img.RotateFlip(RotateFlipType.Rotate90FlipNone);//right-top  
                    break;
                case 7:
                    img.RotateFlip(RotateFlipType.Rotate270FlipX);
                    break;
                case 8:
                    img.RotateFlip(RotateFlipType.Rotate270FlipNone);//left-bottom  
                    break;
                default:
                    break;
            }
            return (Bitmap)img;
        }
  [HttpPost]
        public ActionResult UploadImg(HttpPostedFileBase file, string dir = "UserImg")
        {
            if (CheckImg(file) != "ok") return Json(new { Success = false, Message = "文件格式不對!" }, JsonRequestBehavior.AllowGet);

            if (file != null)
            {
                var path = "~/Content/UploadFiles/" + dir + "/";
                var uploadpath = Server.MapPath(path);
                if (!Directory.Exists(uploadpath))
                {
                    Directory.CreateDirectory(uploadpath);
                }
                string fileName = Path.GetFileName(file.FileName);// 原始文件名稱
                string fileExtension = Path.GetExtension(fileName); // 文件擴展名
                //string saveName = Guid.NewGuid() + fileExtension; // 保存文件名稱 這是個好方法。
                string saveName = Encrypt.GenerateOrderNumber() + fileExtension; // 保存文件名稱 這是個好方法。
                var saveUrl = uploadpath + saveName;
               // file.SaveAs(saveUrl);
                System.Drawing.Image image = ImageManageHelper.RotateImage(file.InputStream); image.Save(saveUrl); if (file.ContentLength / 1024 > 500)//大於0.5M
                {
                    var _saveName = Encrypt.GenerateOrderNumber() + "_thumbnailUrl" + fileExtension;
                    var thumbnailUrl = uploadpath + _saveName;
                    var maxh = 400;
                    var maxw = 400;
                    
                    if (image.Width>maxw)
                    {
                        maxh = 400*image.Height/image.Width;
                    }

                    ImageManageHelper.GetPicThumbnail(saveUrl, thumbnailUrl, maxh, maxw, 90);
                    return Json(new { Success = true, SaveName = "/Content/UploadFiles/Mobile/"  +_saveName });
                }
                return Json(new { Success = true, SaveName = "/Content/UploadFiles/Mobile/" + saveName });
            }
            return Json(new { Success = false, Message = "請選擇要上傳的文件!" }, JsonRequestBehavior.AllowGet);

        }

 

3.圖片半截

 這個問題在安卓上面會有,不是加載的問題。正確的效果圖片應該是垂直居中的。但不知道爲何偶爾的會只出來個半截。並且我發現,給圖片設置百分比,手機和pc不同,手機圖片的百分比並非相對於其父類元素,而是它本身。

 

因此圖片的寬度會超出其父類,即便<div><img></div>的寬度都是100%。overflow:hidden吧,圖片可能顯示不全。超出的部分會致使用戶能夠在圖片上面左右滑動,這在ios中有個搞笑的現象,就是對彈出的圖片不斷的左右滑動,再恢復後竟然能讓原先綁定的點擊事件失效,不肯定是框架的緣由仍是系統的緣由。當時是用一個模態框改造的。最後乾脆本身再寫一個:

Client.modalImg = function (src) {
    if (!src) return;
    if ($(".img-overlay").length) {
        $(".img-overlay").remove();
    };
    var modal = '<div class="img-overlay"> <div class="imgheader"></div> <div class="imgbox"><img onload="reCalcuImg();" src="' + src + '" /></div>';
    $("body").append(modal);
};

//校準位置
function reCalcuImg() {
    var headerH = $(".imgheader").height();
    var boxH = $(".imgbox").height();
    var imgH = $(".imgbox img").height();
    var realMaxH = boxH - headerH;
    // console.log("headerH", headerH, "boxH", boxH, "imgH", imgH, "realMaxH", realMaxH);
    if (imgH > realMaxH) {
        $(".imgbox img").css("height", realMaxH + "px");
        console.log("大於最大高度,realMaxH", realMaxH);

    } else {
        var gap = (realMaxH - imgH) / 2;
        // console.log("小於最大高度,margintop",(realMaxH - imgH), gap);
        $(".imgbox img").css("margin-top", gap + "px");
    }
}

寫在onload事件結束後是確保圖片已經加載完成。這樣才能計算,若是直接在modalimg中計算,圖片的高度可能爲0。而後若是圖片的高度大於最大高度則設置爲最大高度,不然的話在進行margin,讓其垂直居中。

如今使用的是photoSwipe插件。須要結合圖片的onload事件先存下圖片的原始寬高。

//圖片加載完成後調用
function imgloading(img,srcoll) {
 console.log("imgloading", img.height);
  var cached = {
        height: img.naturalHeight,
        width: img.naturalWidth,
        src: img.src
    };

    imClient.imgCache[img.src] = cached;
    srcoll&&imClient.scroll();
}

獲取原始寬高是爲了顯示時候的比例天然:

 //圖片放大
    imClient.imgCache = {};
       var ispop = false;
    function getaimg(src) {
           if (ispop) return;

        function loadaction(w,h) {
            imClient.debugSay("圖片加載:w" + w + " h:" + h);
            loadimg(src, w, h);
            ispop = false;
        }

        var cached = imClient.imgCache[src];
        if (cached) {
            ispop = true;
            loadaction(cached.height, cached.width);
            return;
        }
        console.log("未加載");
    }
    $(document).on("click", ".bubbleimgright img,.bubbleimgleft img", function (e) {
        if ($(this).hasClass("msgfailimg")) return;
        var url = $(this).attr("src");//就放大縮略圖
         getaimg(url);

    });
    function loadimg(url, hei, width) {
        var pswpElement = document.querySelectorAll('.pswp')[0];
        var maxH = $("#historylist").height();
        var maxW = $("#historylist").width();
        var fH = 400;
        var fW = 400;
        //若是都比默認的小
        if (hei <= maxH && width <= maxW) {
            fH = hei;
            fW = width;
        }
        if (hei > maxH && width < maxW) {
            fH = maxH;
            fW = Math.ceil((maxH * width) / hei);
        }
        if (width > maxW) {
            fW = maxW;
            fH = Math.ceil((maxW * hei) / width);
        }
        // build items array
        var items = [
            {
                src: url,
                w: fW,
                h: fH
            }
        ];
        // define options (if needed)
        var options = {
            index: 0 // start at first slide
        };
        var gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, items, options);
        gallery.init();
    }

 

 

5、消息

1.websocket是脆弱但又頑強的。

websocket很容易受到網絡的影響而中斷,但網絡一恢復能自動重連。而手機會有切到後臺運行的這種狀況,好比小米系統會在手機黑屏以後把網絡斷掉,用戶進入應用的時候,你得有重連的機制,確保消息沒有遺漏。

 socket.on("reconnect", function () {
                isConneted = true;
                eventManger.trigger("reconnect");
                listenChannel();
   });

在listenChannel中經過 socket.emit 告之node後端重連了,拿消息的姿式調整下。

but紅米手機有一款socketio總是重連,因此手機上也要準備輪詢的機制。重連三次關掉socket,直接輪詢。

2.消息先發後到。

先發的消息後到,這是頗有可能的,可是用戶就會奇怪。好比一條長消息分紅幾回發,前端能夠在前一條發完了再發第二條。

        var limit = 1000;
        function loop(i) {
            if (i < num) {
                send(content.substr(i * limit, limit), function () {
                    i++;
                    loop(i);
                });
            }
        };
        loop(0);

6、前端的一些思考

那天走在路上想,前端是個什麼樣的職位,會JavaScript,node,熟悉mvvm,html5特性就ok了嗎,其實技能只佔到一部分。前端至關因而個鏈接者,產品經理的想法,美工的設計,後端程序員的接口實現,客戶的期待,測試妹妹的重點,領導巡視的對象,大部分都聚集在前端完成的頁面上,因此溝通,理解能力比較重要。產品經理和測試也會容易太專一於界面效果而忽略整個系統的分配合做。有時候看到界面或者流程不對,多是後端的問題,你得去推。就像看見一人面有難色,其實體內已經有病了,因此你得會表達,表達問題在哪,或者表達須要怎樣的協助;而在面對bug的時候要清楚哪些是功能性的bug,哪些是體驗性的bug,基本功能必須保障正確,體驗性的問題凡是影響基本功能的使用的都是嚴重的體驗性bug,但bug老是有的,不太可能作到全部機型都同樣。這裏面還有時間成本,你解決一個不過重要的bug可能會花上很長的時間;再就是分析思考,各類不正常現象如何找到緣由,除了依賴於經驗你要堅信全部妖魔鬼怪都是有出處的,每一個界面均可能有本身的差別性,不正常的現象重現的條件是什麼,有哪些相關因素,而後順藤摸瓜;再就是代碼能力,儘可能保證模塊的獨立和職責的單一,這個方面細節不少,是每一個程序員須要注意的地方。最後就是想說,神奇的bug還不止這些,由於缺乏這方面的開發經驗和知識儲備因此花了很長時間,遠超本身的預估。文中若有不當或更好的方法還請指出。

相關文章
相關標籤/搜索