js .hover css :active
触发时机 touchend之后1~2个渲染帧 touchstart和touchend之间
阻止传播 可以 无法
多指 基本满足 基本不满足
体验问题 touchend后才触发,不够快 webkit核active时有元素显现,默认会阻止click事件传播,包括html上的onclick写法。解决办法:active样式移到和click同一元素上,或在显现的元素上加pointer-events: none;


实现效果对比

js .hover
inner
css :active
inner

hover 代码

(function ($) {
    $.extend($.fn, {
        /**
         * @param {String} s,动态绑定的对象,多个的话用逗号分隔
         * @param {Number} time,tap最少响应时间,和客户端一致150ms
         * @demo $(o).hover({s: 'xx, yy'})
         */
        hover: function (options) {
            const opts = {
                s: '',
                time: 150
            }

            // hover空间下存储当前对象
            const $cur = $('');
            // 存储多触点st
            const _t = {};
            let isMoved = false;

            const _requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame;

            for (let i in options) {
                opts[i] = options[i]
            }

            $(this).on('touchstart', opts.s, function (e) {
                const touches = e.touches;
                $cur = $(this);
                for (let i = 0; i < touches.length; i++) {
                    // 过滤未离开的、相临同一dom的
                    if (_t[touches[i].identifier] === undefined && (i == 0 || (i && (touches[i].target != touches[i - 1].target)))) {
                        _t[touches[i].identifier] = setTimeout(function () {
                            $cur.addClass('hover')
                        }, opts.time)
                    }
                }
                isMoved = false
            }).on('touchmove', opts.s, function (e) {
                // touchmove执行过快,简化处理
                // 假定用户只用单指滑动
                const touches = e.touches;
                isMoved = true;
                $(this).removeClass('hover');
                clearTimeout(_t[touches[0].identifier]);
                delete (_t[touches[0].identifier])
            }).on('touchend', opts.s, function (e) {
                const touches = e.changedTouches;
                // 局部存储,保证逐个获得处理
                const $cur = $(this);
                // 浏览器目前输出一个,就不做touches.length循环了
                // 清除不需要的st,以防st排队
                clearTimeout(_t[touches[0].identifier]);
                delete (_t[touches[0].identifier]);
                if (isMoved || $cur.hasClass('hover')) {
                    $cur.removeClass('hover');
                    $cur = $('')
                } else {
                    $cur.addClass('hover');
                    if (_requestAnimationFrame) {
                        // 使用2个requestAnimationFrame重绘新帧
                        _requestAnimationFrame(function () {
                            if ($cur.hasClass('hover')) {
                                _requestAnimationFrame(function () {
                                    $cur.removeClass('hover')
                                })
                            }
                        })
                    } else {
                        // 1000/60取整,数字过小,部分手机会忽略而立即执行
                        setTimeout(function () {
                            $cur.removeClass('hover')
                        }, 17)
                    }
                }
            }).on('touchcancel', opts.s, function (e) {
                const touches = e.changedTouches;
                $cur = $(this);
                clearTimeout(_t[touches[0].identifier]);
                delete (_t[touches[0].identifier]);
                $cur.removeClass('hover');
                $cur = $('')
            })
        }
    })
})($)