移动开发hack.bug

[Usage]: FormData XMLHttpRequest Level 2 新增的一个对象,利用它来提交表单、模拟表单提交,当然最大的优势就是可以上传二进制文件!


<form action="" id="form">
    <!-- multiple 属性可选择多文件 -->
    <input type="file" name="" value="选择文件">
    <input type="text" name="username" value="jack ma">
</form>

var form = document.getElementById("form"),
    formData = new FormData(form);

/**
 * formData.get(name: string)
 * 
 * 返回第一个属性为 name 的 值 || null
*/

/**
 * formData.getAll(name: string)
 * 
 * 获取所有属性名为 name 的值,以 数组 || [] 形式返回
*/

/**
 * (同步操作)
 * formData.append(name: string, value: string|Blob, fileName: string) 
 * 
 * 可添加多个同名的值,不会覆盖,非唯一性 
*/
formData.append("username", "jackie");
console.log("formData:", formData, "get:", formData.get("username"), "getAll:", formData.getAll("username"));

/**
 * (同步操作)
 * formData.set(name: string, value: string|Blob, fileName: string)
 * 
 * 有该属性则修改,无则添加该属性及值
 * 会将 name 属性的值全部改为 value,且 getAll 会返回 仅包含一个该value值 的数组
*/
formData.set("username", "setted-name");
console.log("formData:", formData, "get:", formData.get("username"), "getAll:", formData.getAll("username"));


/**
 * has(name: string): Boolean
*/
console.log("has:", formData.has("username"), formData.has("has-attr"));

/**
 * delete(name: string)
 * 
 * 删除所有 name 属性 
*/
formData.delete("username")
console.log("deleted-has:", formData.has("username"), formData.get("username"), formData.getAll("username"));

// ************************************************************************************************************************

### 多图上传

// 通过 <input type="file" multiple >
$.each($input[0].files, function (index, file) {
    formData.append("file" + index, file);
});

$.ajax({
    // ...

    // 直接将 formData 对象传递给后台
    data: formData, 
})

[Usage]: Base64、Blob、File 之间的相互转换


// 1、file、blob 转换为 base64
function fileOrBlobToBase64 (fileOrBlob, callback) {
    var reader = new FileReader();
    reader.readAsDataURL(fileOrBlob);
    reader.onload = function(evt) {
        console.log(reader.result, this.result, evt.target.result); //获取到base64格式图片
        // callback(result);
    };
}

// 2、base64 转换为 blob
function base64ToBlob(base64Data) {
    //console.log(base64Data);//data:image/png;base64,
    var byteString;
    if(base64Data.split(',')[0].indexOf('base64') >= 0)
        byteString = atob(base64Data.split(',')[1]);//base64 解码
    else{
        byteString = unescape(base64Data.split(',')[1]);
    }
    var mimeString = base64Data.split(',')[0].split(':')[1].split(';')[0];//mime类型 -- image/png

    // var arrayBuffer = new ArrayBuffer(byteString.length); //创建缓冲数组
    // var ia = new Uint8Array(arrayBuffer);//创建视图
    var ia = new Uint8Array(byteString.length);//创建视图
    for(var i = 0; i < byteString.length; i++) {
        ia[i] = byteString.charCodeAt(i);
    }
    var blob = new Blob([ia], {
        type: mimeString
    });
    return blob;
}

// 3、base64 转换为 file
function base64ToFile(base64, filename) {//将base64转换为文件
  var arr = base64.split(','), mime = arr[0].match(/:(.*?);/)[1],
      bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
  while(n--){
      u8arr[n] = bstr.charCodeAt(n);
  }
  return new File([u8arr], filename, {type:mime});
}

[Usage]: 富文本编辑器

https://braft.margox.cn/demos/basic


[Usage]: 移动端调试控制台

<script src="//cdn.bootcss.com/vConsole/3.3.3/vconsole.min.js"></script>
// 实例化即可
var VConsole = new VConsole();

[Usage]: IScroll 插件

  • iscroll.js 文件只包含基本功能
<div id="scroll-wrapper">
    <!-- 唯一子元素 为滚动内容 -->
    <div>
        <li>1</li>
        <li>2</li>
        <li>3</li>
        <!-- ... -->
    </div>
</div>
#scroll-wrapper {
    /* 需要知道容器(垂直滚动时)高度 或 (水平滚动时)宽度 */
    height: 800px;
    /* 必要 */
    overflow: hidden; /* 如垂直滑动,当与 IOS 滑动冲突时,可尝试 overflow-y: scroll */

    /* 可选,用于处理 IScroll 滚动区域不准确问题 */
    position: relative;
    /* 可选,解决谷歌浏览器下警告 */
    touch-action: none;
}

/* 当最后一条数据无法滑动到完整显示,可通过给滚动元素加 padding-bottom 解决 */
#scroll-wrapper >div {
    padding-bottom: 20px;
}
var IScrollInstance = new IScroll("#scroll-wrapper", {

});

// request new data...
$("#list").html(str);
// 每次更新滑动列表内容后,记得手动刷新插件
IScrollInstance && IScrollInstance.refresh();


// iscroll-probe.js 才可监听到
IScrollInstance.on("scroll", funciton () {

});

IScrollInstance.on("scrollEnd", _scrollEnd);

function _scrollEnd(e) {
    /*
        【this.maxScrollY】 页面最大滚动距离,若垂直滑动时为负数,其性质等同于 scrollHeight
        【this.y】 已滚动距离
        【this.pointY】 当前手指位置
    */

    // 手指滑出滚动区域后,使滑动内容回弹
    // if (this.pointY < 0)
    //     _IScrollInstance.scrollTo(0, this.maxScrollY, 100);

    // 加载更多!    maxScrollY 和 y 都为负数
    if (this.y < 0 && _IScrollInstance.maxScrollY >= _IScrollInstance.y) { 

    }
}

[Usage]: IScroll-pro 下拉刷新、上拉加载插件

插件源码及用例 地址:[[https://github.com/baiJiXianSheng/IScroll-pro]]


[Usage]: window.postMessage

H5 API:window.postMessage(message: any, targetOrigin: string);

当通过 iframe 嵌入页面时

  • 父页面向 iframe 嵌套的子页面传递消息通知:iframe.contentWindow.postMessage() ```ts

// 父页面 document.querySelect("iframe").contentWindow.postMessage({ res: 1, evtType: "showNotice" }, "*");

// 在子页面定义接收事件 window.addEventListener("message", _postMessage);

function _postMessage (evt) { if (evt.data.evtType === "showNotice") { // use evt.data.res to do something ...

}

}


- iframe 包裹的子页面 向父页面传递消息:`window.parent.postMessage()`
```ts

// 子页面
// 若多层 iframe 嵌套时,可使用 window.top 取得最顶层页面window
window.parent.postMessage({ res: 1, evtType: "showNotice" }, "*");


// 父页面,定义接收消息事件处理函数
window.addEventListener("message", _postMessage);

function _postMessage (evt) {
    if (evt.data.evtType === "showNotice") {
        // use evt.data.res to do something ...

    }
}

[Usage]: 禁止 input 聚焦时调用第三方输入法(兼容性不高)

<input style="ime-mode:disabled" />

[Usage]: 禁止显示 input 历史输入

<input autocomplete="off" />

[Usage]: IOS 下 input 聚焦时光标位置偏移。以input上边界开始到文本底部,IOS 下 input 默认行高100%?

  • (1) 该法可能会出现文字垂直居中效果不准确

    input {
      /* 如原本以 40px 垂直居中 */
      /* line-height: 40px; */
    
      /* 但通常 font-size 和 line-height值相同时,会出现文字被“裁剪”显示不全 */
      font-size: 20px;
    
      line-height: 20px; 
      margin: 10px 0;
    
      /* 若使用 padding: 10px 0; 在ios的高版本如12,会出现bug,当你点击到padding-top的区间的时候,input的光标会移动到input默认文字的最前面,而不是我们希望的最后面 */
    }
    
  • (2)

    .parentDom {
      display: flex;
      align-items: center;
    
      input {
          font-size: 20px;
      }
    }
    

[Usage]: IOS input 键盘 换行 -> 搜索

外面嵌套一层 form

<form action="">
    <input type="search">
</form>

[Usage]: IOS 原生滑动

.box {
    height: 100%;
    overflow-y: scroll;
    -webkit-overflow-scrolling: touch;
}

[Usage]: input[type=search] 时安卓或谷歌浏览器 输入框右侧蓝色x按钮

input::-webkit-search-cancel-button {
    display: none;
}

[Usage]: input 输入文本跳转搜索页面后,再次返回该页面时输入框自动聚焦

// ...

searchInput.value = "";
if (document.activeElement instanceof HTMLElement) {
    document.activeElement.blur();
}

beforeRequest();

[Usage]: iPhone 设备 media 查询 样式适配


/* iphoneX、iphoneXs */
@media only screen and (device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) {
    div {

    }
}
/* iphone Xs Max */
@media only screen and (device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio:3) {
    div {

    }
}
/* iphone XR */
@media only screen and (device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio:2) {
    div {

    }
}

[Usage]: CSS 伪元素 ::before ::after


div::before {
    /* 默认为 inline 元素 */
    display: block; 

    /*
    值得类型可选:'文本'、url(xxx.png)、attr(节点的某个属性)、counter()序列
    **/
    content: '\0252';
    content: '内容';
    content: url(assets/xxx.png);

    /* <div data-index="1"></div> */
    /* 可通过js修改 data-index 的值,使得 伪元素的内容自动改变 */
    content: attr(data-index);

}

BFC

  • float 的值不是 none
  • position 的值不是 static或者relative
  • display 的值是 inline-blocktable-cellflextable-captioninline-flex
  • overflow 的值不是 visible

[Bug]: Input[type=file]

  • h5调用设备相机

手机QQ软件内置浏览器,需要添加 capture="camera" 属性方可调用相机。

html

<!-- 若默认在 input 元素上添加 capture="camera" 属性,则某些浏览器(如safari?) 无法唤起相机 -->

<input type="file" id="loadInput" hidden="" accept="image/*" />

js


var u = navigator.userAgent;
var isAndroid = u.indexOf('Android') > -1 || u.indexOf('Linux') > -1; //g
var isIOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); //ios终端

if (isIOS) {
    $("#loadInput").removeAttr("capture");
} else {
    var browser = navigator.userAgent.toLowerCase();
    if (browser.match(/MicroMessenger/i) == 'micromessenger') {
    // 微信浏览器
        // 可直接调用相机

    } else if(browser.indexOf(' qq') != -1 && browser.indexOf('mqqbrowser') != -1){
    // 手机QQ软件 内置浏览器
        // 需要添加 capture="camera" 属性方可调用相机
        $("#loadInput").attr("capture","camera");

    } else if(browser.indexOf('mqqbrowser') != -1 && browser.indexOf(" qq") == -1){
    // QQ浏览器
        // alert("QQ浏览器");
    } else {
        // alert("以上都不是");
    }
}

// 主动触发 input-file 点击选择文件事件
document.getElementById("loadInput").click();

[Bug]: IOS 下 当有 input 聚焦时,其他元素通过 click 绑定的点击事件,因 input 失去焦点阻止了 click 事件触发,需要二次点击方可触发


    var _isIphone = navigator.userAgent.toUpperCase().indexOf("IPHONE") > -1;
    // hack:IOS 上点击登录按钮时,若输入框聚焦,首先会触发 blur 事件,click事件无法触发,需再次点击触发
    if (_isIphone) {
        $("body").on("touchend", function(e) { 
            // 当点击到 btn 上时,直接调用对应的方法
            if (e.target.id == "btn")
                dosomething();
        })
    } else {
        $("#btn").click(function () {
            dosomething0();
        });
    }

[Bug]:input 聚焦时,底部footer position: fixed 失效被键盘顶起。尝试解决方案:聚焦时 隐藏footer,失去焦点时 显示footer


// 浏览器当前的高度
var oHeight = $(document).height();
$(window).resize(function () {
    var now = $(document).height();
    if (now < oHeight) {
        $(".footer").hide();
    } else {
        $(".footer").show();
    }
});

[Bug]: h5 页面在 IOS 下返回,未重载

// 解决 h5 在IOS 下返回,页面不重载问题
var browserRule = /^.*((iPhone)|(iPad)|(Safari))+.*$/;
if (browserRule.test(navigator.userAgent)) {
    window.onpageshow = function(event) {
        console.log("onpageshow!")
        if (event.persisted) {
            window.location.reload();
        }
    };
}

[Bug]: h5 页面在 Android 下 window.history.go(-1) 无法返回上一页


var u = navigator.userAgent;
var isIOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); //ios终端

$("#cancel-btn").click(function () {

    if (isIOS)
        window.history.go(-1);
    else
        window.location.href = "lastPage.html";

    // 或者 无论IOS或Android,都直接通过 location.href = "lastPage.html"; 跳转到指定页面
});

[Bug]: <video> 标签在 IOS 下点击无法播放

  • 解决方案一:

// 用一张播放按钮的图片,监听其点击事件,触发时动态生成 video 标签插入,并调用 video.play();

### 注:若在初始化加载html文档时,<video></video> 或 <source> 标签没有 src 属性及值,通过动态赋值(不会重新请求?)可能无法正常加载。

$("#play-btn").click(function () {

    var $video = $("<video src='"+ url +"' controls></video>");

    // 可监听其 播放事件
    $video.on("play", function () {  });

    $("#video-container").append($video);

    $video.get(0).play();
});

[Bug]: iframe 在IOS下宽度变宽,产生左右滑动


<div class="iframe-box">
    <iframe src=".." frameborder="0" height="100%"></iframe>
  </div>

.iframe-box {
    overflow: auto; /* 或者 overflow-y: scroll */
    -webkit-overflow-scrolling:touch; /* IOS 顺滑 */
    width:100%;
    height:100%;
}

/* 关键步骤 */
.iframe-box iframe {
    width: 1px; 
    min-width: 100%; 
    *width: 100%;
}

var u = navigator.userAgent;
var isIOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); //ios终端

// 通过 js 设置 scrolling 属性
$("iframe").attr("scrolling", isIOS ? "no" : "auto");

JSRUN前端笔记, 是针对前端工程师开放的一个笔记分享平台,是前端工程师记录重点、分享经验的一个笔记本。JSRUN前端采用的 MarkDown 语法 (极客专用语法), 这里属于IT工程师。