《WebKit 技术内幕》学习之十三(1):移动WebKit

news2024/11/15 23:51:27

1 触控和手势事件

1.1 HTML5规范

        随着电容屏幕的流行,触控操作变得前所未有的流行起来。时至今日,带有多点触控功能已经成为了移动设备的标准配置,基于触控的手势识别技术也获得巨大的发展,如使用两个手指来缩放应用的大小等。所以,在移动系统中,编程需要考虑的不是鼠标事件,而是触控和手势事件,这些事件对于改善用户体验起了非常大的作用。最早将触控和手势事件引入Web领域的是苹果公司,它在iOS2.0中加入了这种支持,随后Android系统也加入了这一阵营。

        在介绍规范之前,有必要先理解一下触控、手势事件与浏览器默认行为的关系。图13-1描述了处理触控事件的可能情况,图中灰色圆圈表示的是一个触控点,当它向上移动的时候,浏览器已面临艰难选择,对于用户触发的触控事件,可能有两个地方需要使用到触控事件:第一是浏览器本身,浏览器可能希望利用这个事件完成翻页动作;另外一方面,该灰色圆圈的部分所对应的元素可能需要由自己来处理这些触控事件,而不是浏览器来处理。浏览器或者WebKit的具体处理逻辑我们在稍后会介绍到。

                                    图13-1 浏览器处理的触控事件

        目前,Web领域引入两种与触控相关的技术,其一是HTML5 Touch Events,它基本上已经成为了规范,得到了众多渲染引擎和浏览器的支持和认可。其二是Gesture Events,它是苹果公司设计并在Safari浏览器中实现的,但是没有得到其他更多浏览器的支持。下面分别来分析这两者。

        首先是HTML5 Touch Events,它已经成为推荐的规范,而且事实上也得到了两家主流移动操作系统中浏览器的支持,可以说发展得非常好,该标准主要是定义如何将原始的触控事件以特定的方式传递给JavaScript引擎,然后再传递给注册的事件响应函数。这一规范在HTML5网页应用中已经比较成熟,网页开发者可以根据规范进行定义,其中最主要的接口是TouchEvent,定义在图13-2中上半部分,表示一次传递给JavaScript注册函数的事件。

                                图13-2 HTML5 Touch Events定义的TouchEvent接口

         根据标准中的定义,TouchEvent分成4种类型:touchstart、touchmove、touchend和touchcancel。熟悉触控事件的读者可能很容易理解,它们分别表示触控点开始接触屏幕、触控点移动、触控点离开屏幕和触控点取消。最后一个类型理解起来比较困难,有时浏览器取消该触控点,可能因为其他一些原因,如它可能进入了其他的窗口等。TouchEvent当然还是继承自DOM的UIEvent,这表明它有同其他事件类似的处理方式,不同点在于这个事件有一些不同的属性。下面逐一来分析它们。

  • “touches” :表示当前屏幕中包括的所有触控点,“touches”是一个列表,如果触控点大于1,表示这是一个支持多点触控的设备。
  • “targetTouches” :表示的是当前所有起始于当前DOM元素的触控点,也就是如果一个触控点的“touchstart”事件发生的位置在该元素的区域内,那就会被包含在该列表中。
  • “changedTouches” :表示发生变化的触控点。如果类型是“touchstart”,那就包含新的触控点。如果是“touchmove”,那就包含发生移动的触控点。而“touchend”就是指触控点移出了屏幕。

每个触控点都需要包含很多信息,也就是图13-2中的众多属性,主要是标记属性的唯一ID、触控的目标(也就是对于的DOM元素)、屏幕位置、视图中的位置等,看起来还是比较直观的。有了这些接口,JavaScript代码能够非常清楚地知道每个触控点的信息,就能够像本地代码一样使用它们来满足各种应用的需求。

使用的方法并不复杂,示例代码13-1展示了如何注册监听事件的处理函数,这同其他的DOM事件区别并不是特别大,而且也只能注册在特定的元素(称为Clickable Element)上,如“div”等。因为TouchEvent有四种类型,示例代码定义了其中三种类型触控事件的处理函数。以“touchstart”为例,它会接受一个事件,就是之前定义的TouchEvent接口,为了避免同浏览器行为的冲突,可以在最开始调用“preventDefault”,这在第5章也做过介绍。后面可以根据事件来做出相应的动作。

示例代码13-1 使用HTML5 Touch Events的JavaScript代码

    var targetElement = document.getElementById("aTouchableElement");
    targetElement.addEventListener("touchstart", onTouchStartEvent, false);
    targetElement.addEventListener("touchmove", onTouchMoveEvent, false);
    targetElement.addEventListener("touchend", onTouchEndEvent,false);
    
    function onTouchStartEvent(event) {
      // 处理事件
      event.preventDefault();
      event.touches;
      event.targetTouches;
      event.changedTouches;
    }
    …

        有了这些原始的触控事件,Web开发者可以在网页中使用JavaScript代码来识别这些原始触控事件并生成手势事件,如Long Press、Pinch、Swipe、Fling等手势事件。目前有很多库提供这样的实现,如jQuery Mobile、Sencha Touch等,这极大地方便了Web开发者。

        除了原始的触控事件,苹果公司开发的Safari浏览器还支持向JavaScript代码提供Gesture Events,其含义是由浏览器来识别原始事件并将手势事件传递给JavaScript代码,当然它定义了一个新的GestureEvent接口,事件类型也分为gesturestart、gesturechange和gestureend。这里的手势事件并没有与上面定义的Pinch等采用同样的方式,而是将旋转角度和缩放大小数据传递给JavaScript,这更像是支持两个手指的触控事件。由于它的局限性和不够通用,所以并没有得到像原始触控事件一样比较广泛的支持,这里也不做过多的介绍。

1.2 工作原理

        WebKit和Chromium是如何支持触控事件的呢?其实这是比较复杂的过程,特别是某些处理方式跟鼠标事件其实还是有不一样的地方。首先事件的派送机制依然是使用第5章介绍的捕获和冒泡机制,具体参看图5-18的过程。

图13-3描述WebKit处理触控事件所使用到的一些主要类和它们之间的关系。最下层的WebWidget和WebView是WebKit的Chromium移植提供的接口,同之前介绍的一样,它们也是被Chromium项目的代码所调用,当Chromium接收到事件之后会将其传给WebViewImpl这个非常重要的类来处理。这个类大家应该很熟悉了,因为已经见过很多次面了。因为事件有多种类型,WebViewImpl类借助于PageWidgetDelegate类来处理和区分这些输入事件。经过PageWidgetDelegate类处理后的事件会调用WebViewImpl类各个事件处理接口,而WebViewImpl类的这些接口基本上使用主框(Frame)的事件处理句柄EventHandler对象来处理事件。细心的读者可以发现,图中的EventHandler包含两个函数,第一个是处理原始触控事件的函数,第二则是处理手势事件的函数。为什么会这样呢?

                                图13-3 WebKit处理触控事件的基础设施

        WebKit除了接收原始的触控事件之外,还需要它的移植或者说是浏览器提供手势事件,这些事件会触发WebKit的默认动作。例如“LongPress”事件,它表示手指在屏幕上长按一段时间,这需要浏览器将其识别成手势事件然后传递给WebKit,当WebKit接收到这个事件之后,触发自己的默认动作。这个事件同前面介绍的Safari提供的Gesture Events不是一回事,因为Safari只是提供了旋转和缩放的值给JavaScript,而这里的手势事件包括一个或者多个手指触发的动作,WebKit并不会将这里的手势事件传递给JavaScript代码,如“longPress”事件会触发浏览器弹出右键菜单的动作。

        对于多框结构的网页,事件首先由WebKit交给主框处理,WebKit会检查该事件是否需要由子框处理,如果是的话,WebKit会将该事件派发给子Frame,依此类推。这是一个递归过程,请读者结合第三章介绍的框结构来理解该过程。

        下面来分析一下在Chromium中浏览器是如何处理从系统传递过来的触控事件,并将它们转换成之后的手势事件的。这一过程稍显复杂,让我们来解释一下原因。当触控事件发生后,Chromium首先需要将触控事件保存,然后使用众多的手势识别器(Gesture Recognizers)来将其识别成手势事件。此时,如下面所描述的问题来了。

  • Chromium是否需要将所有的原始触控事件传递给网页呢?答案是否定的。如一些网页并没有注册监听函数来处理它们,那么就会造成极大的浪费,因为这些事件的传递和处理是个稍长的过程。更为致命的是,一个简单的用户操作通常有非常多的事件,这会极大地浪费CPU等资源。
  • 为什么需要Gesture Event传递给WebKit呢?因为是由浏览器识别并将识别出结果的事件传递给WebKit,这客观上能够有效减少很多事件的传递。
  • 除了发送TouchEvent和GestureEvent之外,也可能会发送MouseEvent,这是为什么?原因很简单,因为目前还存在一些网页,它们需要监听鼠标相关的事件以完成特定的动作,如果Chromium不模拟这些事件,那么网页显然不能正常的工作。但是某些鼠标事件可以模拟,如MouseDown其实对应于TouchStart,MouseUp对应用TouchEnd等。但是MouseOver就比较麻烦,比如一些网页需要根据当前鼠标悬浮事件来显示一个菜单,这对于触控设备来说,的确是一个问题。

        对于Chromium来说,事件的处理还是相当复杂的,因为需要三种类型的事件并将其传递给WebKit。由于触控事件最初是应用在移动设备上的,所以这里也主要以Chromium的Android版为例来介绍,而Chromium的桌面版对触控事件的支持目前还不是特别完善。

        在Chromium的Android版中,所有的事件都是由Android系统传送过来的,这也意味着事件的处理首先是在Java层,当然是在Browser进程的主线程中,如图13-4所示为层次结构图和相关层次中的基础设施。Java层主要包含两个类。

图13-4 Chromium处理触控事件的基础设施

  • ContentViewGestureHandler :它主要有几个任务。首先,它需要通过相应的设施来决定是否需要原始的触控事件,这其实依赖于WebKit,在每个“touchstart”事件开始的时候,需要进行HitTest检查,该动作检查当前触控点所对应的元素,然后检查该元素是否注册了监听事件的函数,如果是,需要将原始事件传送给WebKit。其次是各种手势事件的识别器,它们能够对WebKit所需要的各种手势进行识别并传递给WebKit,最后根据需要(如果有鼠标事件的监听函数)模拟鼠标事件。
  • ContentViewCore类 :主要负责将C++中的功能桥接到Java层中,并将Java中处理好的事件等信息桥接到C++代码中,它对应C++中的类是ContentViewCoreImpl。

        两个类主要负责Java层的事件处理和传递。

        在Java层之下是著名的RenderWidgetHostView类,它表示一个网页的视图。虽然这是Browser进程中的代理类,表示的是Renderer进程中相应的网页视图,它被ContentViewCoreImpl用来将事件传递给Renderer进程。后面大家应该比较清楚了,Chromium通过IPC机制来完成传递,Browser进程中的基础类是ImmediateInputRouter,而Renderer进程中的基础类是RenderWidget类。

        在Renderer进程中,RenderWidget在Chromium中表示网页的结构,它拥有前面WebKit定义的接口类WebWidget,这样,完整的过程就被这些类串联起来了,如图13-4所示。

1.3 启示和实践

        示例代码13-1其实是一个非常典型的用法,只是对于鼠标、触控等类型的事件处理过程可能需要复杂一些的步骤。

        网页除了可能需要自身处理触控事件以外,还有一个比较特别的问题,那就是对于一个为移动设备定制的网页,它可能不需要使用缩放网页(使用Pinch手势的浏览器默认行为来放大或者缩放网页)或者不需要翻滚网页(Fling手势的浏览器默认行为是滚动网页),因开发者已经考虑并设计出了适合移动设备网页阅读的网页了。那有没有办法帮助开发者和浏览器合力规避浏览器默认行为呢?

        根据上面的描述,相信读者已经看出一些端倪了,那就是网页开发者可以注册事件的响应函数,并调用“preventDefault”函数来阻碍浏览器执行默认行为。问题是这一方法只是针对某个元素而已,而不是整个网页,只是当手指触控到该元素的时候才禁止默认行为。解决这一问题的方法很简单,那就是可以将函数注册到区域更大的元素,如示例代码13-2所示的使用“body”元素就可以解决这个问题。

        示例代码13-2 使用触控事件的响应函数来禁止网页的方法和滚动的代码

    function handleEvent(event) {
      event.preventDefault();
      …
    }
    document.body.addEventListener('touchstart', handleEvent, false);
    document.body.addEventListener('touchmove', handleEvent, false);
    document.body.addEventListener('touchend', handleEvent, false);

        这个方法看起来还是需要一些代码,虽然只是短短的不到十行代码,但是除此以外还有一个更好更简单的办法,那就是使用“meta”标签。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1410262.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【多线程】ThreadLocal 详解,举例说明

不理解多线程的同学可先了解多线程理论篇【多线程】线程是什么?多线程为什么?怎么做?通俗易懂讲解多线程 以及多线程进阶篇【多线程】多线程安全,为什么不安全,要怎么做保证其安全,实例 1、ThreadLocal是什…

快速入门Playwright框架:从零到自动化测试的第一步

Playwright框架: 背景介绍: ​ Playwright 是微软开发的 Web应用 的 自动化测试框架 。selenium相对于Playwright慢很多,因为Playwright是异步实现的,但是selenium是同步的,就是后一个操作必须等待前一个操作。 sel…

Python添加、修改和删除列表元素

Python 是一种简洁而强大的编程语言,广泛用于不同领域的软件开发和数据分析中。在 Python 中,列表(List)是一种非常常用的数据类型,用于存储一组元素并按顺序访问。本文将讨论如何在 Python 中对列表进行添加、修改和删…

[极客大挑战 2019]Upload1

直接上传php一句话木马&#xff0c;提示要上传image 把文件名改成gif并加上gif文件头后&#xff0c;绕过了对image类型的检测&#xff0c;但是提示文件内含有<?&#xff0c;且bp抓包后改回php也会被检测 那我们考虑使用js执行php代码 <script languagephp>eval($_PO…

mysql生成最近24小时整点最近30天最近12个月时间临时表

文章目录 生成最近24小时整点生成最近30天生成最近12个月 在统计的时候需要按时间来展示&#xff0c;但是数据的时间不一定是连续的&#xff0c;那就需要在代码里面生成连续的时间&#xff0c;然后按时间匹配到对应的数据&#xff0c;这样比较麻烦&#xff0c;可以在sql中使用连…

Transfomer相关最新研究

文章目录 LogTrans * (有代码&#xff09;TFT &#xff08;有代码&#xff09;InfluTran &#xff08;有代码&#xff09;Informer *&#xff08;有代码&#xff09;&#xff08;长时间&#xff09;ProTranAutoformer ***&#xff08;有代码&#xff09;AliformerPyraformer &a…

[蓝桥学习] 前缀和与差分

前缀和原理 特点 求区间和 如果要实现一边修改一边查询&#xff0c;需要使用树状树组和线段树。 例题 题目很简单&#xff0c;但是代码实现惊艳到我了&#xff0c;是L就加1&#xff0c;是Q就减1&#xff0c;如果区间 [i,j] 是平衡子串的话&#xff0c;那它会在前缀prefix i …

自己本机Video retalking制作数字人

首先需要注意的是&#xff0c;这个要求你的笔记本显存和内存都比较大。我的电脑内存是64G&#xff0c;显卡是8G&#xff0c;操作系统是Windows 11&#xff0c;勉强能够运行出来&#xff0c;但是效果不是很好。 效果如下&#xff0c;无法上传视频&#xff0c;只能通过图片展示出…

C++是如何发展起来的?如何学习C++呢?

一、什么是C C语言是结构化和模块化的语言&#xff0c;适合处理较小规模的程序。对于复杂的问题&#xff0c;规模较大的程序&#xff0c;需要高度的抽象和建模时&#xff0c;C语言则不合适。为了解决软件危机&#xff0c; 20世纪80年代&#xff0c; 计算机界提出了OOP(object …

YOLO 自己训练一个模型

一、准备数据集 我的版本是yolov8 8.11 这个目录结构很重要 ultralytics-main | datasets|coco|train|val 二、训练 编写yaml 文件 # Train/val/test sets as 1) dir: path/to/imgs, 2) file: path/to/imgs.txt, or 3) list: [path/to/imgs1, path/to/imgs2, ..] path…

(超全七大错误)Invalid bound statement (not found): com.xxx.dao.xxxDao.add

1.确保你把dao和mapper都在applicationContext.xml中都扫描了 xml文件 <bean id"sqlSessionFactory" class"org.mybatis.spring.SqlSessionFactoryBean"><property name"dataSource" ref"dataSource"/><property nam…

机器学习算法(一)

一、线性回归 线性回归&#xff08;Linear Regression&#xff09;可能是最流行的机器学习算法。线性回归就是要找一条直线&#xff0c;并且让这条直线尽可能地拟合散点图中的数据点。它试图通过将直线方程与该数据拟合来表示自变量&#xff08;x 值&#xff09;和数值结果&am…

sql管理工具archery简介

在平时的工作过程中&#xff0c;我们肯定会遇到使用sql平台的场景&#xff0c;业内也有很多工具&#xff0c;类似阿里云的dms&#xff0c;但是这个是和云厂商绑定的&#xff0c;我们可能一般没有用到阿里云组件就比较困难了&#xff0c;那还有什么选项了&#xff0c;经过调研&a…

自学C语言-6

第6章 选择结构程序设计 顺序结构程序设计最简单&#xff0c;但通常无法解决生活中的选择性问题。选择结构程序设计需要用到一些条件判断语句&#xff0c;可实现的程序功能更加复杂&#xff0c;程序的逻辑性与灵活性也更加强大。 本章致力于使读者掌握使用if语句进行条件判断的…

14.点亮 LED 灯

14.点亮 LED 灯 1. 应用层操控硬件的两种方式1.1 sysfs 文件系统1.2 sysfs 与 /sys1.3 总结 2. LED 硬件控制方式3. 编写 LED 应用程序4. 在开发板上测试 1. 应用层操控硬件的两种方式 应用层如何操控底层硬件&#xff0c;同样也是通过文件 I/O 的方式来实现&#xff0c;设备文…

python基础——锁

进程锁 (互斥锁) 进程锁的引入&#xff1a; 模拟抢票程序&#xff1a; from multiprocessing import Process import json import time def show_ticket(i):with open("./tickets.txt",mode"r",encoding"utf-8") as file:ticket json.load(f…

2024.1.22力扣每日一题——最大交换

2024.1.22 题目来源我的题解方法一 暴力法方法一 哈希表贪心方法三 贪心 题目来源 力扣每日一题&#xff1b;题序&#xff1a;670 我的题解 方法一 暴力法 直接暴力对数字中的每两个位置进行交换&#xff0c;然后记录交换后生成数字的最大值 时间复杂度&#xff1a;O( log ⁡…

下拉回显问题案例大全

下拉回显问题案例大全 一、原生js案例 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>下拉框数据回…

13. 高级IO

13. 高级IO 1. 非阻塞 IO1.1 阻塞 IO 与非阻塞 IO 读文件 2. IO 多路复用2.1 何为 IO 多路复用2.2 select()2.3 poll()2.3.1 struct pollfd2.3.2 poll() 返回值2.3.3 示例 3. 异步 IO3.1 O_ASYNC3.2 设置异步 IO 事件的接收进程3.3 示例 4. 优化异步 IO4.1 使用实时信号替换默认…

android:persistent和android:priority的区别,对进程优先级有什么影响?

前言&#xff1a;写的apk因为系统busy给我kill了&#xff0c;(adj 900): kill all background&#xff0c;在AndroidManifest.xml添加android:persistent"true"后&#xff0c;被甲方要求不能这样做&#xff0c;还是得从adj改&#xff0c;把 priority改成1000 android…