摘要:前言這是一個存在很久的歷史問題了,對于這樣一個具有普遍性的問題瀏覽器偏偏沒有給出解決方案,沒有方案還聊個什么別急,別急,接下來我們一起來扒一扒關于軟鍵盤高度和的問題我們先來看一個短片認識一下這個問題問題描述當操作者進行輸入操作的時候,彈起的
前言
這是一個存在很久的歷史問題了,對于這樣一個具有普遍性的問題瀏覽器偏偏沒有給出解決方案,what?沒有方案還聊個什么?
別急,別急,接下來我們一起來扒一扒關于軟鍵盤高度和 input 的問題
我們先來看一個短片認識一下這個問題
問題描述:當操作者進行輸入操作的時候,彈起的軟鍵盤把原本的輸入框遮擋了,導致操作者看不到操作結果
以往的解決方案以往的解決方案:
修改網站的頁面布局,比如本例中 twitter 盡量把 input 放置在中部以上的位置,從布局上盡量避免此類問題
在一些指定設備和瀏覽器中異步獲取 window.innerHeight 進行前后對比而得出鍵盤高度
再來看一下另一種常見輸入框的頁面布局:
在這個場景里,輸入框定位在頁面的最底部,當軟鍵盤彈起時整個視圖窗口頁面向上卷動,到達最底部時停止。恰巧當我們用 h5 來模擬這個效果的時候剛好勉強做到。
這是因為當你首次 fouse 到輸入框的時候軟鍵盤彈出,瀏覽器會使頁面會向上滾動,以確保 input 是可見的,該特性和 document.body.scrollIntoViewIfNeeded 方法是一致的,但是當你 body 的可滾動高度超過窗口高度時還會產生另一個問題:固定元素將隨頁面滾動 如下圖
因此瀏覽器關心的只是 input 是否被覆蓋?實際上是 input 中的光標位置!那么這就解釋了為什么輸入框在底部的時候剛好勉強完成了,因為 input 在頁面的底部時,軟鍵盤彈出勢必會遮擋住 input,因而瀏覽器會向上滾動至輸入框可見的位置。
但是如下圖的效果這樣就無法做到了,因為在輸入框的下面還有一行工具欄,也就是說輸入框并非在最底部的位置,那么瀏覽器在滾動到可視位置時只會確保到 input 可見,而對于工具欄是否可見則并不在瀏覽器的考慮范圍內。
IOING 的解決方案分析綜合來看上面兩種布局方案的問題,都不能完美解決輸入被鍵盤遮擋和底部 footer 不能被頂起的問題,那是不是就沒得法子了?
當然號稱可以讓 HTML5 表現更接近 Native 的 IOING 引擎一定是有解決方案的
我們先來看一段 input 在 IOING 中的表現
我們可以看到在輸入過程中頁面通過滾動來始終保持光標位于可視區域的中心位置,因此在這里我們需要提一個知識點:獲取輸入光標的實時位置,當然這也是一個曲折的過程,在這里我就不擴算話題了,繼續來講原話題
前面說了三個主要的傳統解決方案:
第一個是通過把 input 布局盡量放在頁面頂部,顯然這個不是我們想要的,否決掉
把 input 放在最底部,用來完成 footer 固定的效果,但是要局限頁面高度不超過窗口高度,我們可以通過自制滾動控件來解除這個限制,那現在需要解決的技術點就變為實現一個模擬滾動控件
通過比對軟鍵盤彈出前后的 window.innerHeight 的高度差來得到鍵盤高度,從而根據這個高度來實現底部定位和輸入劇中,但是該方法局限于不同設備平臺的支持
綜上所述我們總結一下我們要解決的思路和步驟
先來看一下下面的圖片
當鍵盤彈出時,鍵盤高度 = 不可見窗口高度
這個等式是有條件的,只有當 input 在對底部時該等式才成立 (這是上面講過的 scrollIntoViewIfNeeded 的原因)
思考:如果我們能讓該等式成立,且能夠獲取不可見位置高度,是否就能得出鍵盤高度了呢
我們整理好思路一步一步來實現
1.需要將內容放置在虛擬滾動中,在 IOING 像下面這樣就可以創建一個虛擬滾動區域了
頁面內容
傳統頁面可以使用 WebKit 私有屬性“-webkit-overflow-scrolling: touch” 來允許獨立的滾動區域和觸摸回彈,或者使用 iScroll.js 等第三方庫來完成,但是需要注意對 iScroll 使用不當可能會造成性能問題
2.獲取光標位于屏幕中的位置
3.當光標 fouce 時,鍵盤彈起,若 input 被遮擋頁面會進行滾動,但滾動量不確定,因此我們可以強制滾動到底端,即鍵盤完全彈出后主動使窗口向上滾動窗口高度的距離,而實際上窗口只能向上滾動到最底部位置后就不能再向上滾動了,此時獲取頁面的 top.scrollY 即為實際鍵盤高度
得出公式:
可視區域的中心位置 = 鍵盤高度 + (窗口高 - 鍵盤高度)/2
應滾動距離 = 可視區域的中心位置 - 光標offsetTop - (光標被遮擋 ?鍵盤高度 :0)
當然實際操作需要更多的細節,po 出 IOING 中該部分邏輯實現的源代碼:
// IOING 中部分源代碼 // dom 為 input 元素 // scroll 為滾動容器的 Scroll 對象 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 // 光標位置 rangeOffset = dom.getSelectionRangeOffset() // 可見視圖的中心 viewWrapper = factWindowHeight - keyboardHeight - scrollOffsetTop - scrollOffsetBottom viewCenter = keyboardHeight + viewWrapper / 2 scroll.scrollBy(0, getViewOffset(), 600, null, false) // 滾動到不可見區域時 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 // 重置光標位置 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 // 光標居中 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) })
其它的小細節和注意事項:
safari 會受到瀏覽器底部導航欄的影響,會產生20多像素誤差,需要針對考慮
safari 中的 input 光標在執行 transform 3d變換的時候會出現光標停滯的現象,需要執行光標刷新操作
當 input 被操作者主動滑出可視區域外時應處罰鍵盤收起操作,否則在輸入時 scrollIntoViewIfNeeded 效應將導致窗口滾動出現空白的問題
最后總結:
獲取鍵盤高度只是我們的表象,真正解決 html5 帶來的各種問題才是我們的研究課題,也只有掃清這些布局殺手 h5 才能在追趕 Native 的道路上更近一步!
結尾最后的最后我來 po 一下在 IOING 中完成這一步我們需要做什么?
就是這么簡單,IOING 中 input 默認就能擁有自動居中特性
如果你要取消這個特性,就像下面這樣寫
當然也可以設置居中相對底部/相對于頂部的偏移位置
在輸入過程中能夠實時輸出光標位置,且將位置信息賦值給數據源對象
當前光標位置:left: {test.range.left}, top: {test.range.top}
用js 獲取鍵盤高度的方法
//鍵盤彈起時為鍵盤高度,未彈起時為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 })
詳細文檔傳送門:http://ioing.com/#docs-dom-input
GitHub 傳送門:https://github.com/ioing/IOING
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/112430.html
摘要:前言這是一個存在很久的歷史問題了,對于這樣一個具有普遍性的問題瀏覽器偏偏沒有給出解決方案,沒有方案還聊個什么別急,別急,接下來我們一起來扒一扒關于軟鍵盤高度和的問題我們先來看一個短片認識一下這個問題問題描述當操作者進行輸入操作的時候,彈起的 前言 這是一個存在很久的歷史問題了,對于這樣一個具有普遍性的問題瀏覽器偏偏沒有給出解決方案,what?沒有方案還聊個什么? 別急,別急,接下來我們...
摘要:前言這是一個存在很久的歷史問題了,對于這樣一個具有普遍性的問題瀏覽器偏偏沒有給出解決方案,沒有方案還聊個什么別急,別急,接下來我們一起來扒一扒關于軟鍵盤高度和的問題我們先來看一個短片認識一下這個問題問題描述當操作者進行輸入操作的時候,彈起的 前言 這是一個存在很久的歷史問題了,對于這樣一個具有普遍性的問題瀏覽器偏偏沒有給出解決方案,what?沒有方案還聊個什么? 別急,別急,接下來我們...
摘要:前端日報精選機制詳解與中實踐應用基礎與實踐如何用獲取虛擬鍵盤高度適用所有平臺和入門教程阮一峰的網絡日志編程技能提升指南中文到底什么是又是什么眾成翻譯調用模塊騰訊前端團隊社區小書從一個簡單的例子講起小書教程小書優化操作小書教 2017-09-07 前端日報 精選 JavaScript Event Loop 機制詳解與 Vue.js 中實踐應用 Redux 基礎與實踐如何用 js 獲取虛擬...
閱讀 3240·2021-11-15 11:37
閱讀 2458·2021-09-29 09:48
閱讀 3821·2021-09-22 15:55
閱讀 3021·2021-09-22 10:02
閱讀 2641·2021-08-25 09:40
閱讀 3235·2021-08-03 14:03
閱讀 1701·2019-08-29 13:11
閱讀 1575·2019-08-29 12:49