应用示例

控制页面滚动

H5 页面上的滚动,可分为窗口滚动和元素(加了overflow: scroll | auto)滚动。

JavaScript 有以下方法和属性可以控制页面滚动:

常用的滚动方法

scrollTo()

使界面滚动到给定元素的指定坐标位置

scrollTo(x-coord, y-coord)
scrollTo(options)

scroll()

使界面滚动到给定元素的指定坐标位置,与 scrollTo() 方法相同。

window.scroll(x-coord, y-coord) 
window.scroll(options)

scrollBy()

使页面相对当前的位置滚动多少距离。

window.scrollBy(x-coord, y-coord)
window.scrollBy(options) 
  • x-coord 是期望滚动到位置水平轴上距元素左上角的像素

  • y-coord 是期望滚动到位置竖直轴上距元素左上角的像素

  • options 是一个 ScrollToOptions 对象。

    • top:指定 window 或元素 Y 轴方向滚动的像素数。

    • left:指定 window 或元素 X 轴方向滚动的像素数。

    • behavior:指定滚动是否应该平滑进行,还是立即跳到指定位置。取值:smooth (平滑滚动) | instant (瞬间滚动) | auto。默认值 auto ,效果等同于 instant。

scrollIntoView

让当前的元素滚动到浏览器窗口的可视区域内。

element.scrollIntoView(); // 等同于element.scrollIntoView(true) 
element.scrollIntoView(alignToTop); // Boolean型参数 
element.scrollIntoView(scrollIntoViewOptions); // Object型参数
  • alignToTop:一个 Boolean 值。true 表示元素的顶端将和其所在滚动区的可视区域的顶端对齐;false 表示元素的底端将和其所在滚动区的可视区域的底端对齐。

  • scrollIntoViewOptions:一个包含下列属性的对象:

    • behavior:定义动画过渡效果。取值:auto | smooth。默认为 auto。

    • block:定义垂直方向的对齐。取值:start | center | end | nearest。默认为 start。

    • inline:定义水平方向的对齐。取值:start | center | end | nearest。默认为 nearest。

常用的滚动属性

scrollLeft

读取或设置元素滚动条到元素左边的距离。

Element.scrollLeft

注意: 如果这个元素的内容排列方向 direction 是 rtl (right-to-left) ,那么滚动条会位于最右侧(内容开始处),并且 scrollLeft 属性值为 0。此时,当你从右到左拖动滚动条时,scrollLeft 会从 0 变为负数。

scrollLeft 可以是任意整数,然而:

  • 如果元素不能滚动(比如:元素没有溢出),则值为 0。

  • 如果设置的值小于 0,则将变为 0。

  • 如果设置的值大于元素内容最大宽度,那么将被设为元素最大宽度。

scrollTop

获取或设置一个元素的内容垂直滚动的像素数。

Element.scrollTop

一个元素的 scrollTop 值是这个元素的内容顶部(卷起来的)到它的视口可见内容(的顶部)的距离的度量。

其他与 scrollLeft 类似。

其他方法

  • scrollByLines(): 按给定的行数滚动文档。注:目前仅 Firefox 浏览器支持。

  • scrollByPages(): 在当前文档页面按照指定的页数翻页。注:目前仅 Firefox 浏览器支持。

注意问题

  1. 低版本 IE 浏览器可能不支持 scrollTo()、scrollBy() 等方法。

  2. scrollTo()、scrollBy()、scrollBy() 都可以用于 window 对象和 element 对象控制滚动。 但部分手机(比如:华为 麦芒4,系统版本:6.0.1),不支持 element.scrollTo() 。可以使用 element.scrollLeft 属性。

  3. window 对象没有 scrollTop 、scrollLeft 属性,但可以用 document.documentElement.scrollTop、document.documentElement.scrollLeft 实现窗口滚动。

蒙层底部页面跟随滚动

弹窗是一种常见的交互方式,而蒙层是弹窗必不可少的元素,用于隔断页面与弹窗区块,暂时阻断页面的交互。但是,在蒙层元素中滑动的时候,滑到内容的尽头时,再继续滑动,蒙层底部的页面会开始滚动,显然这不是我们想要的效果,因此需要阻止这种行为。

解决方案一

打开蒙层时,给 body 添加样式:

/*在某些机型下,你可能还需要给根节点添加此样式。关闭蒙层时,移除些样式*/
body {
    height: 100%;
    overflow: hidden;
}

**优点:**简单方便,只需添加css样式,没有复杂的逻辑。 **缺点:**兼容性不好,适用于pc,移动端就尴尬了。部分安卓机型以及safari中,无法无法阻止底部页面滚动。

解决方案二

就是利用移动端的touch事件,来阻止默认行为(这里可以理解为页面滚动就是默认行为)。

// node为蒙层容器dom节点
node.addEventListener('touchstart', e => {
    e.preventDefault()
}, false)

简单粗暴,滚动时底部页面也无法动弹了。假如你的蒙层内容不会有滚动条,那么上述方法prefect。 但是,最怕空气突然安静,假如蒙层内容有滚动条的话,那么它再也无法动弹了。因此我们需要写一些js逻辑来判断要不要阻止默认行为,复杂程度明显增加。

**具体思路:**判定蒙层内容是否滚动到尽头,是则阻止默认行为,反之任它横行。

解决方案三(推荐使用)

**具体思路:**要阻止页面滚动,那么何不将其固定在视窗(即position: fixed),这样它就无法滚动了,当蒙层关闭时再释放。 当然还有一些细节要考虑,将页面固定视窗后,内容会回头最顶端,需要记录一下同步top值。

let top = 0
function stopBodyScroll (isFixed) {
    let bodyEl = document.body
    if (isFixed) {
        top = window.scrollY

        bodyEl.style.position = 'fixed'
        bodyEl.style.top = -top + 'px'
    } else {
        bodyEl.style.position = ''
        bodyEl.style.top = ''
        window.scrollTo(0, top) // 回到原先的top
    }
}

iphone安全区域问题

iPhoneX 取消了物理按键,改成底部小黑条,这一改动导致网页出现了比较尴尬的屏幕适配问题。对于网页而言,顶部(刘海部位)的适配问题浏览器已经做了处理,所以我们只需要关注底部与小黑条的适配问题即可(即常见的吸底导航、返回顶部等各种相对底部 fixed 定位的元素)。

viewport-fit

iOS11 新增特性,苹果公司为了适配 iPhoneX 对现有 viewport meta 标签的一个扩展,用于设置网页在可视窗口的布局方式,可设置三个值:

  • contain: 可视窗口完全包含网页内容

  • cover:网页内容完全覆盖可视窗口

  • auto:默认值,跟 contain 表现一致

env() 和 constant()

iOS11 新增特性,Webkit 的一个 CSS 函数,用于设定安全区域与边界的距离,有四个预定义的变量:

  • safe-area-inset-left:安全区域距离左边边界距离

  • safe-area-inset-right:安全区域距离右边边界距离

  • safe-area-inset-top:安全区域距离顶部边界距离

  • safe-area-inset-bottom:安全区域距离底部边界距离

注意:当 viewport-fit=contain 时 env() 是不起作用的,必须要配合 viewport-fit=cover 使用。对于不支持env() 的浏览器,浏览器将会忽略它。

The env() function shipped in iOS 11 with the name constant(). Beginning with Safari Technology Preview 41 and the iOS 11.2 beta, constant() has been removed and replaced with env(). You can use the CSS fallback mechanism to support both versions, if necessary, but should prefer env() going forward.

在 iOS 11中,env()函数的用的名称是constant()。 从Safari Technology Preview 41和 iOS 11.2 beta 版开始,constant() 已经被env() 替换。 如果有必要,可以使用CSS回退机制来支持这两个版本,但应该首选 env()。

/* env() 跟 constant() 需要同时存在,而且顺序不能换 */
padding-bottom: constant(safe-area-inset-bottom); /* 兼容 iOS < 11.2 */
padding-bottom: env(safe-area-inset-bottom); /* 兼容 iOS >= 11.2 */

如何适配?

HTML 文件中设置网页在可视窗口的布局方式:

<meta name="viewport" content="width=device-width, viewport-fit=cover">

CSS 样式中引用变量:

body {
  /*页面主体内容限定在安全区域内*/
  padding-bottom: constant(safe-area-inset-bottom);
  padding-bottom: env(safe-area-inset-bottom);
}

常见问题

  1. iphone X 下,fixed 元素在页面滑动底部时,会往上滑动移动一点。这是由于**页面高度不足一屏(或者撑开页面高度的元素加了fixed,脱离文档流)**引起的。

    解决方法: body 或者其他外层元素中加 min-height: 100%,或者,加空 div 元素、伪元素撑高页面。

  2. 元素使用了类似 height: 100% (这个值是不包括底部安全区域的)来固定高度。如果元素要使用 padding-bottom: env(safe-area-inset-bottom) 加附加小黑条高,元素必需设置box-sizing: content-box

限制input输入

input 元素只允许输入数字、中文、字母等。

实现思路: 添加键盘监听事件,通过正则匹配或键码值对比,将不符合校验的值替换或者将事件对象的returnValue 置为 false。

三个键盘事件:keydown、keypress、keyup。

只能输入或粘贴数字:

<!--this.可以不加-->
<input onkeyup="this.value = this.value.replace(/\D/g,'')" onafterpaste="this.value = this.value.replace(/\D/g,'')">

只能输入数字**(无闪动)**:

<input type="text" onkeypress="if ((event.keyCode<48 || event.keyCode>57)) event.returnValue=false" /> 

只能输入或粘贴数字和小数点:

<input onkeyup="if(isNaN(value)) execCommand('undo')" onafterpaste="if(isNaN(value)) execCommand('undo')">

只能输入或粘贴英文字母和数字,不能输入中文:

<input onkeyup="this.value = this.value.replace(/[^\w\.\/]/g,'')" onafterpaste="this.value = this.value.replace(/[^\w\.\/]/g,'')">

只能输入或粘贴中文:

<input type="text" onkeyup="this.value = this.value.replace(/[^\u4e00-\u9fa5]/g,'')" onafterpaste="this.value = this.value.replace(/[^\u4e00-\u9fa5]/g,'')">  

简易禁止输入汉字:

<!--无效??-->
<input style="ime-mode:disabled">

Click的300ms延迟响应

Click 的 300ms 延迟是由双击缩放 (double tap to zoom)所导致的,由于用户可以进行双击缩放或者双击滚动的操作,当用户一次点击屏幕之后,浏览器并不能立刻判断用户是确实要打开这个链接,还是想要进行双击操作。因此,移动端浏览器就等待 300 毫秒,以判断用户是否再次点击了屏幕。

随着响应式网页逐渐增多,用户使用双击缩放机会减少,这 300ms 的延迟就更不可接受了。浏览器开发商也随之提供相应的解决方案。这些方案在 防止移动设备上 300 毫秒点击延迟的 5 种方法中,被提及的包括:

<!--禁用缩放(Chrome 和 Firefox)-->
<meta name="viewport" content="width=device-width, user-scalable=no">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1">

<!--将视口设置为设备宽度(Chrome 32+)-->
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=3">


<!--非标准的CSS touch-action属性,允许在不禁用缩放的情况下消除特定元素或整个文档的延迟:-->
.my_elements {
	-ms-touch-action: manipulation;	/* IE10  */
	touch-action: manipulation;		/* IE11+ */
}

但这些方案并不完美,需要针对某些版本浏览器,又或仅在 Android 的浏览器上使用。

所以,这时候就需要一个更简单通用的解决方案,其中 FT Labs 专门为解决移动端浏览器 300ms 点击延迟问题所开发的一个轻量级的库 FastClick 就是很好的选择。FastClick 在检测到 touchend 事件的时候,会通过 DOM 自定义事件立即触发一个模拟 click 事件,并把浏览器在 300 毫秒之后真正触发的 click 事件阻止掉。

文字无缝滚动

<section class="box_01">
    <div class="container_01">
        <div class="item_01 item_01_01">【蒹葭】蒹葭苍苍,白露为霜。所谓伊人,在水一方,溯洄从之,道阻且长。溯游从之,宛在水中央。</div>
        <div class="item_01 item_01_02"></div>
    </div>
</section>
.box_01 { overflow: hidden; border: 1px solid #0aa; }
.box_01 .container_01 { white-space: nowrap; }
.box_01 .container_01 .item_01 { display: inline-block; color: #0aa; }
const boxDom01 = document.querySelector('.box_01')
const itemDom01 = document.querySelector('.item_01_01')
const itemDom02 = document.querySelector('.item_01_02')
itemDom02.innerHTML = itemDom01.innerHTML

setInterval(() => {
    if (boxDom01.scrollLeft - itemDom02.offsetWidth >= 0) {
        boxDom01.scrollLeft -= itemDom01.offsetWidth
    } else {
        boxDom01.scrollLeft ++
    }
}, 20)

文字无缝滚动DEMO

防抖和节流

防抖: 在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则重新计时。

function debounce(fn, delay) {
    var timer = null
    return function () {
        var context = this
        var args = arguments
        if (timer) {
            clearTimeout(timer)
        }
        timer = setTimeout(() => {
            fn.apply(context, args)
        }, delay)
    }
}

节流: 规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。

function throttle(fn, delay) {
    var preTime = Date.now()
    return function () {
        var context = this
        var args = arguments
        var nowTime = Date.now()
        if (nowTime - preTime >= delay) {
            preTime = Date.now()
            return fn.apply(context, args)
        }
    }
}

观察者模式源码

var events = (function() {
    var topics = {}
    return {
        // 注册监听函数
        subscribe: function(topic, handler) {
            if (!topics.hasOwnProperty(topic)) {
                topics[topic] = []
            }
            topics[topic].push(handler)
        },

        // 发布事件,触发观察者回调事件
        publish: function(topic, info) {
            if (topics.hasOwnProperty(topic)) {
                topics[topic].forEach(function(handler) {
                    handler(info)
                })
            }
        },

        // 移除主题的一个观察者的回调事件
        remove: function(topic, handler) {
            if (!topics.hasOwnProperty(topic)) return

            var handlerIndex = -1
            topics[topic].forEach(function(item, index) {
                if (item === handler) {
                    handlerIndex = index
                }
            })

            if (handlerIndex >= 0) {
                topics[topic].splice(handlerIndex, 1)
            }
        },

        // 移除主题的所有观察者的回调事件
        removeAll: function(topic) {
            if (topics.hasOwnProperty(topic)) {
                topics[topic] = []
            }
        }
    }
})()

获取光标位置、聚焦、选择内容

获取 input 光标位置:

function getCursor() {
    const input = document.getElementById("input")
    // 非IE浏览器
    if (typeof input.selectionStart == "number") {
        return input.selectionStart;
    } else {
        const range = document.selection.createRange();
        range.moveStart("character", -input.value.length);
        return range.text.length;
    }
    return -1
}

选择 input 中的内容:

function setSelection() {
    const input = document.getElementById("input")
    input.focus();
    input.setSelectionRange(1, 8);
}

最后更新于