摘要:前言這是一個(gè)存在很久的歷史問題了,對(duì)于這樣一個(gè)具有普遍性的問題瀏覽器偏偏沒有給出解決方案,沒有方案還聊個(gè)什么別急,別急,接下來我們一起來扒一扒關(guān)于軟鍵盤高度和的問題我們先來看一個(gè)短片認(rèn)識(shí)一下這個(gè)問題問題描述當(dāng)操作者進(jìn)行輸入操作的時(shí)候,彈起的
前言
這是一個(gè)存在很久的歷史問題了,對(duì)于這樣一個(gè)具有普遍性的問題瀏覽器偏偏沒有給出解決方案,what?沒有方案還聊個(gè)什么?
別急,別急,接下來我們一起來扒一扒關(guān)于軟鍵盤高度和 input 的問題
我們先來看一個(gè)短片認(rèn)識(shí)一下這個(gè)問題
問題描述:當(dāng)操作者進(jìn)行輸入操作的時(shí)候,彈起的軟鍵盤把原本的輸入框遮擋了,導(dǎo)致操作者看不到操作結(jié)果
以往的解決方案以往的解決方案:
修改網(wǎng)站的頁面布局,比如本例中 twitter 盡量把 input 放置在中部以上的位置,從布局上盡量避免此類問題
在一些指定設(shè)備和瀏覽器中異步獲取 window.innerHeight 進(jìn)行前后對(duì)比而得出鍵盤高度
再來看一下另一種常見輸入框的頁面布局:
在這個(gè)場景里,輸入框定位在頁面的最底部,當(dāng)軟鍵盤彈起時(shí)整個(gè)視圖窗口頁面向上卷動(dòng),到達(dá)最底部時(shí)停止。恰巧當(dāng)我們用 h5 來模擬這個(gè)效果的時(shí)候剛好勉強(qiáng)做到。
這是因?yàn)楫?dāng)你首次 fouse 到輸入框的時(shí)候軟鍵盤彈出,瀏覽器會(huì)使頁面會(huì)向上滾動(dòng),以確保 input 是可見的,該特性和 document.body.scrollIntoViewIfNeeded 方法是一致的,但是當(dāng)你 body 的可滾動(dòng)高度超過窗口高度時(shí)還會(huì)產(chǎn)生另一個(gè)問題:固定元素將隨頁面滾動(dòng) 如下圖
因此瀏覽器關(guān)心的只是 input 是否被覆蓋?實(shí)際上是 input 中的光標(biāo)位置!那么這就解釋了為什么輸入框在底部的時(shí)候剛好勉強(qiáng)完成了,因?yàn)?input 在頁面的底部時(shí),軟鍵盤彈出勢(shì)必會(huì)遮擋住 input,因而瀏覽器會(huì)向上滾動(dòng)至輸入框可見的位置。
但是如下圖的效果這樣就無法做到了,因?yàn)樵谳斎肟虻南旅孢€有一行工具欄,也就是說輸入框并非在最底部的位置,那么瀏覽器在滾動(dòng)到可視位置時(shí)只會(huì)確保到 input 可見,而對(duì)于工具欄是否可見則并不在瀏覽器的考慮范圍內(nèi)。
IOING 的解決方案分析綜合來看上面兩種布局方案的問題,都不能完美解決輸入被鍵盤遮擋和底部 footer 不能被頂起的問題,那是不是就沒得法子了?
當(dāng)然號(hào)稱可以讓 HTML5 表現(xiàn)更接近 Native 的 IOING 引擎一定是有解決方案的
我們先來看一段 input 在 IOING 中的表現(xiàn)
我們可以看到在輸入過程中頁面通過滾動(dòng)來始終保持光標(biāo)位于可視區(qū)域的中心位置,因此在這里我們需要提一個(gè)知識(shí)點(diǎn):獲取輸入光標(biāo)的實(shí)時(shí)位置,當(dāng)然這也是一個(gè)曲折的過程,在這里我就不擴(kuò)算話題了,繼續(xù)來講原話題
前面說了三個(gè)主要的傳統(tǒng)解決方案:
第一個(gè)是通過把 input 布局盡量放在頁面頂部,顯然這個(gè)不是我們想要的,否決掉
把 input 放在最底部,用來完成 footer 固定的效果,但是要局限頁面高度不超過窗口高度,我們可以通過自制滾動(dòng)控件來解除這個(gè)限制,那現(xiàn)在需要解決的技術(shù)點(diǎn)就變?yōu)閷?shí)現(xiàn)一個(gè)模擬滾動(dòng)控件
通過比對(duì)軟鍵盤彈出前后的 window.innerHeight 的高度差來得到鍵盤高度,從而根據(jù)這個(gè)高度來實(shí)現(xiàn)底部定位和輸入劇中,但是該方法局限于不同設(shè)備平臺(tái)的支持
綜上所述我們總結(jié)一下我們要解決的思路和步驟
先來看一下下面的圖片
當(dāng)鍵盤彈出時(shí),鍵盤高度 = 不可見窗口高度
這個(gè)等式是有條件的,只有當(dāng) input 在對(duì)底部時(shí)該等式才成立 (這是上面講過的 scrollIntoViewIfNeeded 的原因)
思考:如果我們能讓該等式成立,且能夠獲取不可見位置高度,是否就能得出鍵盤高度了呢
我們整理好思路一步一步來實(shí)現(xiàn)
1.需要將內(nèi)容放置在虛擬滾動(dòng)中,在 IOING 像下面這樣就可以創(chuàng)建一個(gè)虛擬滾動(dòng)區(qū)域了
頁面內(nèi)容
傳統(tǒng)頁面可以使用 WebKit 私有屬性“-webkit-overflow-scrolling: touch” 來允許獨(dú)立的滾動(dòng)區(qū)域和觸摸回彈,或者使用 iScroll.js 等第三方庫來完成,但是需要注意對(duì) iScroll 使用不當(dāng)可能會(huì)造成性能問題
2.獲取光標(biāo)位于屏幕中的位置
3.當(dāng)光標(biāo) fouce 時(shí),鍵盤彈起,若 input 被遮擋頁面會(huì)進(jìn)行滾動(dòng),但滾動(dòng)量不確定,因此我們可以強(qiáng)制滾動(dòng)到底端,即鍵盤完全彈出后主動(dòng)使窗口向上滾動(dòng)窗口高度的距離,而實(shí)際上窗口只能向上滾動(dòng)到最底部位置后就不能再向上滾動(dòng)了,此時(shí)獲取頁面的 top.scrollY 即為實(shí)際鍵盤高度
得出公式:
可視區(qū)域的中心位置 = 鍵盤高度 + (窗口高 - 鍵盤高度)/2
應(yīng)滾動(dòng)距離 = 可視區(qū)域的中心位置 - 光標(biāo)offsetTop - (光標(biāo)被遮擋 ?鍵盤高度 :0)
當(dāng)然實(shí)際操作需要更多的細(xì)節(jié),po 出 IOING 中該部分邏輯實(shí)現(xiàn)的源代碼:
// IOING 中部分源代碼 // dom 為 input 元素 // scroll 為滾動(dòng)容器的 Scroll 對(duì)象 function scrollTo (y, _y, t, s, r) { r = r == undefined ? 1 : r y = y == undefined ? top.scrollY : y if ( r == 1 ? y > _y : y < _y) return s = s == undefined ? Math.abs((_y - y) / t * 17.6) : s rAF(function () { top.scrollTo(0, y += r*s) scrollTo(y, _y, t, s, r) }) } function visibility () { if ( this.moving || this.wheeling ) { var top = dom.offset().top var height = dom.offsetHeight var viewTop = keyboardHeight + scrollOffsetTop var viewBottom = factWindowHeight - scrollOffsetBottom if ( top + height <= viewTop || top >= viewBottom ) { dom.blur() } } } function refreshCursor () { rAF(function () { dom.getSelectionRangeInsert("") }) } function getScroll () { var scroller = reactScroller || dom.closest("scroll") scroll = scroller ? scroller.scrollEvent : null if ( type == 1 ) { minScrollY = scroll.minScrollY } } function getViewOffset () { // android : (top.scrollY == 0 ? keyboardHeight : 0) viewOffset = viewCenter - rangeOffset.top - (top.scrollY == 0 ? keyboardHeight : 0) + (that.module.config.sandbox ? keyboardHeight : 0) return viewOffset } function keyboardUp (e) { getScroll(1) if ( !scroll ) return // refresh cursor {{ if ( device.os.ios && device.os.iosVersion < 12 ) { scroll.on("scroll scrollend", refreshCursor) } // }} if ( normal ) return function upend (e) { window.keyboard.height = keyboardHeight = top.scrollY || factWindowHeight - top.innerHeight // change minScrollY scroll.minScrollY = minScrollY + keyboardHeight scroll.options.minScrollY = scroll.minScrollY // 光標(biāo)位置 rangeOffset = dom.getSelectionRangeOffset() // 可見視圖的中心 viewWrapper = factWindowHeight - keyboardHeight - scrollOffsetTop - scrollOffsetBottom viewCenter = keyboardHeight + viewWrapper / 2 scroll.scrollBy(0, getViewOffset(), 600, null, false) // 滾動(dòng)到不可見區(qū)域時(shí) blur scroll.on("scroll", visibility) window.trigger("keyboardup", { height : keyboardHeight }) if ( reactResize ) { scrollTo(null, 0, 300, null, -1) } } setTimeout(function () { top.one("scrollend", upend) // no scroll setTimeout(function () { if ( keyboardHeight == 0 ) upend() }, 300) // ``` old var offset = 0 if ( device.os.mobileSafari && device.os.iosVersion < 12 ) { offset = 24 * viewportScale } // scroll to bottom scrollTo(null, viewportHeight - offset, 300, null, 1) }, 300) } function keyboardDown () { getScroll() if ( !scroll ) return // ``` old : refresh cursor {{ if ( device.os.ios && device.os.iosVersion < 11 ) { scroll.off("scroll scrollend", refreshCursor) } // }} if ( normal ) return if ( keyboardHeight == 0 ) return false top.scrollTo(0, 0) scroll.wrapper.scrollTop = 0 // change minScrollY scroll.minScrollY = minScrollY scroll.options.minScrollY = minScrollY scroll.off("scroll", visibility) scroll._refresh() window.keyboard.height = keyboardHeight = 0 } function selectionRange (e) { getScroll() if ( !scroll ) return // 非箭頭按鍵取消 if ( e.type == "keyup" && ![8, 13, 37, 38, 39, 40].consistOf(e.keyCode) ) return // 重置光標(biāo)位置 if ( reactOffset ) { rangeOffset = dom.getSelectionRangeOffset() } else if ( reactPosition ) { rangeOffset = dom.getSelectionRangePosition() } if ( reactOrigin && rangeOffset ) { rangeOffset.each(function (i, v) { scope.setValueOfHref(reactOrigin + "." + i, v) }) } if ( normal ) return // 光標(biāo)居中 if ( e.type == "input" && e.timeStamp - timeStamp < 2000 ) return if ( !scroll || !viewCenter ) return if ( !reactOffset ) { rangeOffset = dom.getSelectionRangeOffset() } timeStamp = e.timeStamp scroll.scrollBy(0, getViewOffset(), 400, null, false) } dom.on("click", checkChange) dom.on("focus", keyboardUp) dom.on("blur", keyboardDown) dom.on("focus keyup input paste mouseup", selectionRange) })
其它的小細(xì)節(jié)和注意事項(xiàng):
safari 會(huì)受到瀏覽器底部導(dǎo)航欄的影響,會(huì)產(chǎn)生20多像素誤差,需要針對(duì)考慮
safari 中的 input 光標(biāo)在執(zhí)行 transform 3d變換的時(shí)候會(huì)出現(xiàn)光標(biāo)停滯的現(xiàn)象,需要執(zhí)行光標(biāo)刷新操作
當(dāng) input 被操作者主動(dòng)滑出可視區(qū)域外時(shí)應(yīng)處罰鍵盤收起操作,否則在輸入時(shí) scrollIntoViewIfNeeded 效應(yīng)將導(dǎo)致窗口滾動(dòng)出現(xiàn)空白的問題
最后總結(jié):
獲取鍵盤高度只是我們的表象,真正解決 html5 帶來的各種問題才是我們的研究課題,也只有掃清這些布局殺手 h5 才能在追趕 Native 的道路上更近一步!
結(jié)尾最后的最后我來 po 一下在 IOING 中完成這一步我們需要做什么?
就是這么簡單,IOING 中 input 默認(rèn)就能擁有自動(dòng)居中特性
如果你要取消這個(gè)特性,就像下面這樣寫
當(dāng)然也可以設(shè)置居中相對(duì)底部/相對(duì)于頂部的偏移位置
在輸入過程中能夠?qū)崟r(shí)輸出光標(biāo)位置,且將位置信息賦值給數(shù)據(jù)源對(duì)象
當(dāng)前光標(biāo)位置:left: {test.range.left}, top: {test.range.top}
用js 獲取鍵盤高度的方法
//鍵盤彈起時(shí)為鍵盤高度,未彈起時(shí)為0 console.log(window.keyboard.height) // 通過鍵盤彈起事件獲取 window.on("keyboardup", function (e) { console.log(e.height) }) // 鍵盤收起事件 window.on("keyboarddown", function (e) { console.log(e.height) // 0 })
詳細(xì)文檔傳送門:http://ioing.com/#docs-dom-input
GitHub 傳送門:https://github.com/ioing/IOING
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/112430.html
摘要:前言這是一個(gè)存在很久的歷史問題了,對(duì)于這樣一個(gè)具有普遍性的問題瀏覽器偏偏沒有給出解決方案,沒有方案還聊個(gè)什么別急,別急,接下來我們一起來扒一扒關(guān)于軟鍵盤高度和的問題我們先來看一個(gè)短片認(rèn)識(shí)一下這個(gè)問題問題描述當(dāng)操作者進(jìn)行輸入操作的時(shí)候,彈起的 前言 這是一個(gè)存在很久的歷史問題了,對(duì)于這樣一個(gè)具有普遍性的問題瀏覽器偏偏沒有給出解決方案,what?沒有方案還聊個(gè)什么? 別急,別急,接下來我們...
摘要:前言這是一個(gè)存在很久的歷史問題了,對(duì)于這樣一個(gè)具有普遍性的問題瀏覽器偏偏沒有給出解決方案,沒有方案還聊個(gè)什么別急,別急,接下來我們一起來扒一扒關(guān)于軟鍵盤高度和的問題我們先來看一個(gè)短片認(rèn)識(shí)一下這個(gè)問題問題描述當(dāng)操作者進(jìn)行輸入操作的時(shí)候,彈起的 前言 這是一個(gè)存在很久的歷史問題了,對(duì)于這樣一個(gè)具有普遍性的問題瀏覽器偏偏沒有給出解決方案,what?沒有方案還聊個(gè)什么? 別急,別急,接下來我們...
摘要:前端日?qǐng)?bào)精選機(jī)制詳解與中實(shí)踐應(yīng)用基礎(chǔ)與實(shí)踐如何用獲取虛擬鍵盤高度適用所有平臺(tái)和入門教程阮一峰的網(wǎng)絡(luò)日志編程技能提升指南中文到底什么是又是什么眾成翻譯調(diào)用模塊騰訊前端團(tuán)隊(duì)社區(qū)小書從一個(gè)簡單的例子講起小書教程小書優(yōu)化操作小書教 2017-09-07 前端日?qǐng)?bào) 精選 JavaScript Event Loop 機(jī)制詳解與 Vue.js 中實(shí)踐應(yīng)用 Redux 基礎(chǔ)與實(shí)踐如何用 js 獲取虛擬...
摘要:目前,常用的模塊規(guī)范主要有兩種和。攔截全局請(qǐng)求一直接引入腳本攔截需要的回調(diào)或函數(shù)。深刻知道一個(gè)良好的命名規(guī)范的重要性,同時(shí)在項(xiàng)目中也會(huì)遇到一些命名的瓶頸。 基于 Three.js 的超快的 3D 開發(fā)框架:Whitestorm.js Whitestorm.js 是一款基于 Three.js 超快的 Web 應(yīng)用 3D 開發(fā)框架。它為普通的 Three.js 任務(wù)提供封裝、使搭建環(huán)境、...
閱讀 3244·2021-11-15 11:37
閱讀 2460·2021-09-29 09:48
閱讀 3827·2021-09-22 15:55
閱讀 3023·2021-09-22 10:02
閱讀 2646·2021-08-25 09:40
閱讀 3238·2021-08-03 14:03
閱讀 1705·2019-08-29 13:11
閱讀 1579·2019-08-29 12:49