CINXE.COM
Multi-touch web development | Articles | web.dev
<!doctype html> <html lang="en" dir="ltr"> <head> <meta name="google-signin-client-id" content="157101835696-ooapojlodmuabs2do2vuhhnf90bccmoi.apps.googleusercontent.com"> <meta name="google-signin-scope" content="profile email https://www.googleapis.com/auth/developerprofiles https://www.googleapis.com/auth/developerprofiles.award"> <meta property="og:site_name" content="web.dev"> <meta property="og:type" content="website"><meta name="theme-color" content="#3740ff"><meta charset="utf-8"> <meta content="IE=Edge" http-equiv="X-UA-Compatible"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="manifest" href="/_pwa/web/manifest.json" crossorigin="use-credentials"> <link rel="preconnect" href="//www.gstatic.com" crossorigin> <link rel="preconnect" href="//fonts.gstatic.com" crossorigin> <link rel="preconnect" href="//fonts.googleapis.com" crossorigin> <link rel="preconnect" href="//apis.google.com" crossorigin> <link rel="preconnect" href="//www.google-analytics.com" crossorigin><link rel="stylesheet" href="//fonts.googleapis.com/css?family=Google+Sans:400,500|Roboto:400,400italic,500,500italic,700,700italic|Roboto+Mono:400,500,700&display=swap"> <link rel="stylesheet" href="//fonts.googleapis.com/css2?family=Material+Icons&family=Material+Symbols+Outlined&display=block"><link rel="stylesheet" href="https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/web/css/app.css"> <link rel="stylesheet" href="https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/web/css/dark-theme.css" disabled> <link rel="shortcut icon" href="https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/web/images/favicon.png"> <link rel="apple-touch-icon" href="https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/web/images/touchicon-180.png"><link rel="canonical" href="https://web.dev/articles/mobile-touch"><link rel="search" type="application/opensearchdescription+xml" title="web.dev" href="https://web.dev/s/opensearch.xml"> <link rel="alternate" hreflang="en" href="https://web.dev/articles/mobile-touch" /><link rel="alternate" hreflang="x-default" href="https://web.dev/articles/mobile-touch" /><link rel="alternate" hreflang="ar" href="https://web.dev/articles/mobile-touch?hl=ar" /><link rel="alternate" hreflang="bn" href="https://web.dev/articles/mobile-touch?hl=bn" /><link rel="alternate" hreflang="zh-Hans" href="https://web.dev/articles/mobile-touch?hl=zh-cn" /><link rel="alternate" hreflang="zh-Hant" href="https://web.dev/articles/mobile-touch?hl=zh-tw" /><link rel="alternate" hreflang="fa" href="https://web.dev/articles/mobile-touch?hl=fa" /><link rel="alternate" hreflang="fr" href="https://web.dev/articles/mobile-touch?hl=fr" /><link rel="alternate" hreflang="de" href="https://web.dev/articles/mobile-touch?hl=de" /><link rel="alternate" hreflang="he" href="https://web.dev/articles/mobile-touch?hl=he" /><link rel="alternate" hreflang="hi" href="https://web.dev/articles/mobile-touch?hl=hi" /><link rel="alternate" hreflang="id" href="https://web.dev/articles/mobile-touch?hl=id" /><link rel="alternate" hreflang="it" href="https://web.dev/articles/mobile-touch?hl=it" /><link rel="alternate" hreflang="ja" href="https://web.dev/articles/mobile-touch?hl=ja" /><link rel="alternate" hreflang="ko" href="https://web.dev/articles/mobile-touch?hl=ko" /><link rel="alternate" hreflang="pl" href="https://web.dev/articles/mobile-touch?hl=pl" /><link rel="alternate" hreflang="pt-BR" href="https://web.dev/articles/mobile-touch?hl=pt-br" /><link rel="alternate" hreflang="ru" href="https://web.dev/articles/mobile-touch?hl=ru" /><link rel="alternate" hreflang="es-419" href="https://web.dev/articles/mobile-touch?hl=es-419" /><link rel="alternate" hreflang="th" href="https://web.dev/articles/mobile-touch?hl=th" /><link rel="alternate" hreflang="tr" href="https://web.dev/articles/mobile-touch?hl=tr" /><link rel="alternate" hreflang="vi" href="https://web.dev/articles/mobile-touch?hl=vi" /><link rel="alternate" hreflang="en-cn" href="https://web.developers.google.cn/articles/mobile-touch" /><link rel="alternate" hreflang="x-default" href="https://web.developers.google.cn/articles/mobile-touch" /><link rel="alternate" hreflang="ar-cn" href="https://web.developers.google.cn/articles/mobile-touch?hl=ar" /><link rel="alternate" hreflang="bn-cn" href="https://web.developers.google.cn/articles/mobile-touch?hl=bn" /><link rel="alternate" hreflang="zh-Hans-cn" href="https://web.developers.google.cn/articles/mobile-touch?hl=zh-cn" /><link rel="alternate" hreflang="zh-Hant-cn" href="https://web.developers.google.cn/articles/mobile-touch?hl=zh-tw" /><link rel="alternate" hreflang="fa-cn" href="https://web.developers.google.cn/articles/mobile-touch?hl=fa" /><link rel="alternate" hreflang="fr-cn" href="https://web.developers.google.cn/articles/mobile-touch?hl=fr" /><link rel="alternate" hreflang="de-cn" href="https://web.developers.google.cn/articles/mobile-touch?hl=de" /><link rel="alternate" hreflang="he-cn" href="https://web.developers.google.cn/articles/mobile-touch?hl=he" /><link rel="alternate" hreflang="hi-cn" href="https://web.developers.google.cn/articles/mobile-touch?hl=hi" /><link rel="alternate" hreflang="id-cn" href="https://web.developers.google.cn/articles/mobile-touch?hl=id" /><link rel="alternate" hreflang="it-cn" href="https://web.developers.google.cn/articles/mobile-touch?hl=it" /><link rel="alternate" hreflang="ja-cn" href="https://web.developers.google.cn/articles/mobile-touch?hl=ja" /><link rel="alternate" hreflang="ko-cn" href="https://web.developers.google.cn/articles/mobile-touch?hl=ko" /><link rel="alternate" hreflang="pl-cn" href="https://web.developers.google.cn/articles/mobile-touch?hl=pl" /><link rel="alternate" hreflang="pt-BR-cn" href="https://web.developers.google.cn/articles/mobile-touch?hl=pt-br" /><link rel="alternate" hreflang="ru-cn" href="https://web.developers.google.cn/articles/mobile-touch?hl=ru" /><link rel="alternate" hreflang="es-419-cn" href="https://web.developers.google.cn/articles/mobile-touch?hl=es-419" /><link rel="alternate" hreflang="th-cn" href="https://web.developers.google.cn/articles/mobile-touch?hl=th" /><link rel="alternate" hreflang="tr-cn" href="https://web.developers.google.cn/articles/mobile-touch?hl=tr" /><link rel="alternate" hreflang="vi-cn" href="https://web.developers.google.cn/articles/mobile-touch?hl=vi" /><title>Multi-touch web development | Articles | web.dev</title> <meta property="og:title" content="Multi-touch web development | Articles | web.dev"><meta property="og:url" content="https://web.dev/articles/mobile-touch"><meta property="og:locale" content="en"><script type="application/ld+json"> { "@context": "https://schema.org", "@type": "Article", "dateModified": "2011-08-21", "headline": "Multi-touch web development" } </script><script type="application/ld+json"> { "@context": "https://schema.org", "@type": "BreadcrumbList", "itemListElement": [{ "@type": "ListItem", "position": 1, "name": "Articles", "item": "https://web.dev/articles" },{ "@type": "ListItem", "position": 2, "name": "Multi-touch web development", "item": "https://web.dev/articles/mobile-touch" }] } </script> <link rel="stylesheet" href="/extras.css"></head> <body class="" template="page" theme="web-theme" type="article" appearance layout="docs" display-toc pending> <devsite-progress type="indeterminate" id="app-progress"></devsite-progress> <section class="devsite-wrapper"> <devsite-cookie-notification-bar></devsite-cookie-notification-bar><devsite-header role="banner"> <div class="devsite-header--inner nocontent"> <div class="devsite-top-logo-row-wrapper-wrapper"> <div class="devsite-top-logo-row-wrapper"> <div class="devsite-top-logo-row"> <button type="button" id="devsite-hamburger-menu" class="devsite-header-icon-button button-flat material-icons gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Navigation menu button" visually-hidden aria-label="Open menu"> </button> <div class="devsite-product-name-wrapper"> <a href="/" class="devsite-site-logo-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Site logo" track-type="globalNav" track-name="webDev" track-metadata-position="nav" track-metadata-eventDetail="nav"> <picture> <source srcset="https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/web/images/lockup-dark-theme.svg" media="(prefers-color-scheme: dark)" class="devsite-dark-theme" alt="web.dev"> <img src="https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/web/images/lockup.svg" class="devsite-site-logo" alt="web.dev"> </picture> </a> <span class="devsite-product-name"> <ul class="devsite-breadcrumb-list" > <li class="devsite-breadcrumb-item "> </li> </ul> </span> </div> <div class="devsite-top-logo-row-middle"> <div class="devsite-header-upper-tabs"> <devsite-tabs class="upper-tabs"> <nav class="devsite-tabs-wrapper" aria-label="Upper tabs"> <tab > <a href="https://web.dev/about" track-metadata-eventdetail="https://web.dev/about" class="devsite-tabs-content gc-analytics-event " track-type="nav" track-metadata-position="nav - about" track-metadata-module="primary nav" data-category="Site-Wide Custom Events" data-label="Tab: About" track-name="about" > About </a> </tab> <tab > <a href="https://web.dev/html" track-metadata-eventdetail="https://web.dev/html" class="devsite-tabs-content gc-analytics-event " track-type="nav" track-metadata-position="nav - html" track-metadata-module="primary nav" data-category="Site-Wide Custom Events" data-label="Tab: HTML" track-name="html" > HTML </a> </tab> <tab > <a href="https://web.dev/css" track-metadata-eventdetail="https://web.dev/css" class="devsite-tabs-content gc-analytics-event " track-type="nav" track-metadata-position="nav - css" track-metadata-module="primary nav" data-category="Site-Wide Custom Events" data-label="Tab: CSS" track-name="css" > CSS </a> </tab> <tab > <a href="https://web.dev/javascript" track-metadata-eventdetail="https://web.dev/javascript" class="devsite-tabs-content gc-analytics-event " track-type="nav" track-metadata-position="nav - javascript" track-metadata-module="primary nav" data-category="Site-Wide Custom Events" data-label="Tab: JavaScript" track-name="javascript" > JavaScript </a> </tab> <tab > <a href="https://web.dev/blog" track-metadata-eventdetail="https://web.dev/blog" class="devsite-tabs-content gc-analytics-event " track-type="nav" track-metadata-position="nav - blog" track-metadata-module="primary nav" data-category="Site-Wide Custom Events" data-label="Tab: Blog" track-name="blog" > Blog </a> </tab> <tab > <a href="https://web.dev/learn" track-metadata-eventdetail="https://web.dev/learn" class="devsite-tabs-content gc-analytics-event " track-type="nav" track-metadata-position="nav - learn" track-metadata-module="primary nav" data-category="Site-Wide Custom Events" data-label="Tab: Learn" track-name="learn" > Learn </a> </tab> <tab > <a href="https://web.dev/explore" track-metadata-eventdetail="https://web.dev/explore" class="devsite-tabs-content gc-analytics-event " track-type="nav" track-metadata-position="nav - explore" track-metadata-module="primary nav" data-category="Site-Wide Custom Events" data-label="Tab: Explore" track-name="explore" > Explore </a> </tab> <tab > <a href="https://web.dev/patterns" track-metadata-eventdetail="https://web.dev/patterns" class="devsite-tabs-content gc-analytics-event " track-type="nav" track-metadata-position="nav - patterns" track-metadata-module="primary nav" data-category="Site-Wide Custom Events" data-label="Tab: Patterns" track-name="patterns" > Patterns </a> </tab> <tab > <a href="https://web.dev/case-studies" track-metadata-eventdetail="https://web.dev/case-studies" class="devsite-tabs-content gc-analytics-event " track-type="nav" track-metadata-position="nav - case studies" track-metadata-module="primary nav" data-category="Site-Wide Custom Events" data-label="Tab: Case studies" track-name="case studies" > Case studies </a> </tab> </nav> </devsite-tabs> </div> <devsite-search enable-signin enable-search enable-suggestions enable-query-completion project-name="Articles" tenant-name="web.dev" > <form class="devsite-search-form" action="https://web.dev/s/results" method="GET"> <div class="devsite-search-container"> <button type="button" search-open class="devsite-search-button devsite-header-icon-button button-flat material-icons" aria-label="Open search"></button> <div class="devsite-searchbox"> <input aria-activedescendant="" aria-autocomplete="list" aria-label="Search" aria-expanded="false" aria-haspopup="listbox" autocomplete="off" class="devsite-search-field devsite-search-query" name="q" placeholder="Search" role="combobox" type="text" value="" > <div class="devsite-search-image material-icons" aria-hidden="true"> </div> <div class="devsite-search-shortcut-icon-container" aria-hidden="true"> <kbd class="devsite-search-shortcut-icon">/</kbd> </div> </div> </div> </form> <button type="button" search-close class="devsite-search-button devsite-header-icon-button button-flat material-icons" aria-label="Close search"></button> </devsite-search> </div> <devsite-appearance-selector></devsite-appearance-selector> <devsite-language-selector> <ul role="presentation"> <li role="presentation"> <a role="menuitem" lang="en" >English</a> </li> <li role="presentation"> <a role="menuitem" lang="de" >Deutsch</a> </li> <li role="presentation"> <a role="menuitem" lang="es_419" >Español – América Latina</a> </li> <li role="presentation"> <a role="menuitem" lang="fr" >Français</a> </li> <li role="presentation"> <a role="menuitem" lang="id" >Indonesia</a> </li> <li role="presentation"> <a role="menuitem" lang="it" >Italiano</a> </li> <li role="presentation"> <a role="menuitem" lang="pl" >Polski</a> </li> <li role="presentation"> <a role="menuitem" lang="pt_br" >Português – Brasil</a> </li> <li role="presentation"> <a role="menuitem" lang="vi" >Tiếng Việt</a> </li> <li role="presentation"> <a role="menuitem" lang="tr" >Türkçe</a> </li> <li role="presentation"> <a role="menuitem" lang="ru" >Русский</a> </li> <li role="presentation"> <a role="menuitem" lang="he" >עברית</a> </li> <li role="presentation"> <a role="menuitem" lang="ar" >العربيّة</a> </li> <li role="presentation"> <a role="menuitem" lang="fa" >فارسی</a> </li> <li role="presentation"> <a role="menuitem" lang="hi" >हिंदी</a> </li> <li role="presentation"> <a role="menuitem" lang="bn" >বাংলা</a> </li> <li role="presentation"> <a role="menuitem" lang="th" >ภาษาไทย</a> </li> <li role="presentation"> <a role="menuitem" lang="zh_cn" >中文 – 简体</a> </li> <li role="presentation"> <a role="menuitem" lang="zh_tw" >中文 – 繁體</a> </li> <li role="presentation"> <a role="menuitem" lang="ja" >日本語</a> </li> <li role="presentation"> <a role="menuitem" lang="ko" >한국어</a> </li> </ul> </devsite-language-selector> <devsite-user enable-profiles id="devsite-user"> <span class="button devsite-top-button" aria-hidden="true" visually-hidden>Sign in</span> </devsite-user> </div> </div> </div> <div class="devsite-collapsible-section devsite-header-no-lower-tabs "> <div class="devsite-header-background"> </div> </div> </div> </devsite-header> <devsite-book-nav scrollbars hidden> <div class="devsite-book-nav-filter" hidden> <span class="filter-list-icon material-icons" aria-hidden="true"></span> <input type="text" placeholder="Filter" aria-label="Type to filter" role="searchbox"> <span class="filter-clear-button hidden" data-title="Clear filter" aria-label="Clear filter" role="button" tabindex="0"></span> </div> <nav class="devsite-book-nav devsite-nav nocontent" aria-label="Side menu"> <div class="devsite-mobile-header"> <button type="button" id="devsite-close-nav" class="devsite-header-icon-button button-flat material-icons gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Close navigation" aria-label="Close navigation"> </button> <div class="devsite-product-name-wrapper"> <a href="/" class="devsite-site-logo-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Site logo" track-type="globalNav" track-name="webDev" track-metadata-position="nav" track-metadata-eventDetail="nav"> <picture> <source srcset="https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/web/images/lockup-dark-theme.svg" media="(prefers-color-scheme: dark)" class="devsite-dark-theme" alt="web.dev"> <img src="https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/web/images/lockup.svg" class="devsite-site-logo" alt="web.dev"> </picture> </a> <span class="devsite-product-name"> <ul class="devsite-breadcrumb-list" > <li class="devsite-breadcrumb-item "> </li> </ul> </span> </div> </div> <div class="devsite-book-nav-wrapper"> <div class="devsite-mobile-nav-top"> <ul class="devsite-nav-list"> <li class="devsite-nav-item"> <a href="/about" class="devsite-nav-title gc-analytics-event " data-category="Site-Wide Custom Events" data-label="Tab: About" track-name="about" data-category="Site-Wide Custom Events" data-label="Responsive Tab: About" track-type="globalNav" track-metadata-eventDetail="globalMenu" track-metadata-position="nav"> <span class="devsite-nav-text" tooltip > About </span> </a> </li> <li class="devsite-nav-item"> <a href="/html" class="devsite-nav-title gc-analytics-event " data-category="Site-Wide Custom Events" data-label="Tab: HTML" track-name="html" data-category="Site-Wide Custom Events" data-label="Responsive Tab: HTML" track-type="globalNav" track-metadata-eventDetail="globalMenu" track-metadata-position="nav"> <span class="devsite-nav-text" tooltip > HTML </span> </a> </li> <li class="devsite-nav-item"> <a href="/css" class="devsite-nav-title gc-analytics-event " data-category="Site-Wide Custom Events" data-label="Tab: CSS" track-name="css" data-category="Site-Wide Custom Events" data-label="Responsive Tab: CSS" track-type="globalNav" track-metadata-eventDetail="globalMenu" track-metadata-position="nav"> <span class="devsite-nav-text" tooltip > CSS </span> </a> </li> <li class="devsite-nav-item"> <a href="/javascript" class="devsite-nav-title gc-analytics-event " data-category="Site-Wide Custom Events" data-label="Tab: JavaScript" track-name="javascript" data-category="Site-Wide Custom Events" data-label="Responsive Tab: JavaScript" track-type="globalNav" track-metadata-eventDetail="globalMenu" track-metadata-position="nav"> <span class="devsite-nav-text" tooltip > JavaScript </span> </a> </li> <li class="devsite-nav-item"> <a href="/blog" class="devsite-nav-title gc-analytics-event " data-category="Site-Wide Custom Events" data-label="Tab: Blog" track-name="blog" data-category="Site-Wide Custom Events" data-label="Responsive Tab: Blog" track-type="globalNav" track-metadata-eventDetail="globalMenu" track-metadata-position="nav"> <span class="devsite-nav-text" tooltip > Blog </span> </a> </li> <li class="devsite-nav-item"> <a href="/learn" class="devsite-nav-title gc-analytics-event " data-category="Site-Wide Custom Events" data-label="Tab: Learn" track-name="learn" data-category="Site-Wide Custom Events" data-label="Responsive Tab: Learn" track-type="globalNav" track-metadata-eventDetail="globalMenu" track-metadata-position="nav"> <span class="devsite-nav-text" tooltip > Learn </span> </a> </li> <li class="devsite-nav-item"> <a href="/explore" class="devsite-nav-title gc-analytics-event " data-category="Site-Wide Custom Events" data-label="Tab: Explore" track-name="explore" data-category="Site-Wide Custom Events" data-label="Responsive Tab: Explore" track-type="globalNav" track-metadata-eventDetail="globalMenu" track-metadata-position="nav"> <span class="devsite-nav-text" tooltip > Explore </span> </a> </li> <li class="devsite-nav-item"> <a href="/patterns" class="devsite-nav-title gc-analytics-event " data-category="Site-Wide Custom Events" data-label="Tab: Patterns" track-name="patterns" data-category="Site-Wide Custom Events" data-label="Responsive Tab: Patterns" track-type="globalNav" track-metadata-eventDetail="globalMenu" track-metadata-position="nav"> <span class="devsite-nav-text" tooltip > Patterns </span> </a> </li> <li class="devsite-nav-item"> <a href="/case-studies" class="devsite-nav-title gc-analytics-event " data-category="Site-Wide Custom Events" data-label="Tab: Case studies" track-name="case studies" data-category="Site-Wide Custom Events" data-label="Responsive Tab: Case studies" track-type="globalNav" track-metadata-eventDetail="globalMenu" track-metadata-position="nav"> <span class="devsite-nav-text" tooltip > Case studies </span> </a> </li> </ul> </div> </div> </nav> </devsite-book-nav> <section id="gc-wrapper"> <main role="main" class="devsite-main-content" has-sidebar > <div class="devsite-sidebar"> <div class="devsite-sidebar-content"> <devsite-toc class="devsite-nav" role="navigation" aria-label="On this page" depth="2" scrollbars ></devsite-toc> <devsite-recommendations-sidebar class="nocontent devsite-nav"> </devsite-recommendations-sidebar> </div> </div> <devsite-content> <article class="devsite-article"> <div class="devsite-article-meta nocontent" role="navigation"> <ul class="devsite-breadcrumb-list" aria-label="Breadcrumb"> <li class="devsite-breadcrumb-item "> <a href="https://web.dev/" class="devsite-breadcrumb-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Breadcrumbs" data-value="1" track-type="globalNav" track-name="breadcrumb" track-metadata-position="1" track-metadata-eventdetail="" > Home </a> </li> <li class="devsite-breadcrumb-item "> <div class="devsite-breadcrumb-guillemet material-icons" aria-hidden="true"></div> <a href="https://web.dev/articles" class="devsite-breadcrumb-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Breadcrumbs" data-value="2" track-type="globalNav" track-name="breadcrumb" track-metadata-position="2" track-metadata-eventdetail="Articles" > Articles </a> </li> </ul> <devsite-thumb-rating position="header"> </devsite-thumb-rating> </div> <h1 class="devsite-page-title" tabindex="-1"> Multi-touch web development </h1> <devsite-feature-tooltip ack-key="AckCollectionsBookmarkTooltipDismiss" analytics-category="Site-Wide Custom Events" analytics-action-show="Callout Profile displayed" analytics-action-close="Callout Profile dismissed" analytics-label="Create Collection Callout" class="devsite-page-bookmark-tooltip nocontent" dismiss-button="true" id="devsite-collections-dropdown" dismiss-button-text="Dismiss" close-button-text="Got it"> <devsite-bookmark></devsite-bookmark> <span slot="popout-heading"> Stay organized with collections </span> <span slot="popout-contents"> Save and categorize content based on your preferences. </span> </devsite-feature-tooltip> <div class="devsite-page-title-meta"><devsite-view-release-notes></devsite-view-release-notes></div> <devsite-toc class="devsite-nav" depth="2" devsite-toc-embedded > </devsite-toc> <div class="devsite-article-body clearfix "> <p> <div class="wd-authors" translate="no"> <div class="wd-author"> <img class="devsite-landing-row-item-icon" alt="Boris Smus" src="https://web.dev/images/authors/smus.jpg" decoding="async" height="64" loading="lazy" width="64"> <div> <span> Boris Smus </span> <div class="wd-author__links"> <a href="https://twitter.com/borismus" aria-label="Boris Smus on X" rel="me"> <svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 300 271"> <title>X</title> <path fill="currentColor" d="m236 0h46l-101 115 118 156h-92.6l-72.5-94.8-83 94.8h-46l107-123-113-148h94.9l65.5 86.6zm-16.1 244h25.5l-165-218h-27.4z"></path> </svg></a> <a href="https://smus.com/" aria-label="Boris Smus's homepage" rel="me"> <svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 30 30"> <title>Homepage</title> <circle cx="14.5" cy="14.5" r="13.5" stroke-width="2" stroke-miterlimit="10" fill="none" stroke="currentColor" /> <ellipse cx="14.5" cy="14.5" rx="6.1" ry="13.5" stroke-width="2" stroke-miterlimit="10" fill="none" stroke="currentColor" /> <path d="M1.6 9.6h25.8M1.6 19.4h25.8" stroke-width="2" stroke-miterlimit="10" fill="none" stroke="currentColor" /> </svg></a> </div> </div> </div> </div></p> <h2 id="introduction" data-text="Introduction" tabindex="-1">Introduction</h2> <p>Mobile devices such as smartphones and tablets usually have a capacitive touch-sensitive screen to capture interactions made with the user's fingers. As the mobile web evolves to enable increasingly sophisticated applications, web developers need a way to handle these events. For example, nearly any fast-paced game requires the player to press multiple buttons at once, which, in the context of a touchscreen, implies multi-touch. </p> <p>Apple introduced their <a href="http://developer.apple.com/library/safari/#documentation/UserExperience/Reference/TouchEventClassReference/TouchEvent/TouchEvent.html#//apple_ref/doc/uid/TP40009358">touch events API</a> in iOS 2.0. Android has been catching up to this de-facto standard and closing the gap. Recently a W3C working group has come together to work on this <a href="http://dvcs.w3.org/hg/webevents/raw-file/tip/touchevents.html">touch events specification</a>.</p> <p>In this article I’ll dive into the touch events API provided by iOS and Android devices, as well as desktop Chrome on hardware that supports touch, and explore what sorts of applications you can build, present some best practices, and cover useful techniques that make it easier to develop touch-enabled applications.</p> <h2 id="touch_events" data-text="Touch events" tabindex="-1">Touch events</h2> <p>Three basic touch events are outlined in the spec and implemented widely across mobile devices:</p> <ul> <li><strong>touchstart</strong>: a finger is placed on a DOM element.</li> <li><strong>touchmove</strong>: a finger is dragged along a DOM element.</li> <li><strong>touchend</strong>: a finger is removed from a DOM element.</li> </ul> <p>Each touch event includes three lists of touches:</p> <ul> <li><strong>touches</strong>: a list of all fingers currently on the screen.</li> <li><strong>targetTouches</strong>: a list of fingers on the current DOM element.</li> <li><strong>changedTouches</strong>: a list of fingers involved in the current event. For example, in a touchend event, this will be the finger that was removed.</li> </ul> <p>These lists consist of objects that contain touch information:</p> <ul> <li><strong>identifier</strong>: a number that uniquely identifies the current finger in the touch session.</li> <li><strong>target</strong>: the DOM element that was the target of the action.</li> <li><strong>client/page/screen coordinates</strong>: where on the screen the action happened.</li> <li><strong>radius coordinates and rotationAngle</strong>: describe the ellipse that approximates finger shape.</li> </ul> <h2 id="touch-enabled_apps" data-text="Touch-enabled apps" tabindex="-1">Touch-enabled apps</h2> <p>The <strong>touchstart</strong>, <strong>touchmove</strong>, and <strong>touchend</strong> events provide a rich enough feature set to support virtually any kind of touch-based interaction – including all of the usual multi-touch gestures like pinch-zoom, rotation, and so on. </p> <p>This snippet lets you drag a DOM element around using single-finger touch:</p> <pre class="prettyprint lang-js" translate="no" dir="ltr"><code translate="no" dir="ltr">var obj = document.getElementById('id'); obj.addEventListener('touchmove', function(event) { // If there's exactly one finger inside this element if (event.targetTouches.length == 1) { var touch = event.targetTouches[0]; // Place element where the finger is obj.style.left = touch.pageX + 'px'; obj.style.top = touch.pageY + 'px'; } }, false); </code></pre> <p>Below is a <a href="https://github.com/borismus/MagicTouch/blob/master/samples/tracker.html">sample</a> that displays all current touches on the screen. It’s useful just to get a feeling for the responsiveness of the device.</p> <figure> <a href="https://github.com/borismus/MagicTouch/blob/master/samples/tracker.html"> <img src="/static/articles/mobile-touch/image/finger-tracking-c3a836001a408.png" alt="Finger tracking." width="650" height="217" srcset="https://web.dev/static/articles/mobile-touch/image/finger-tracking-c3a836001a408_36.png 36w,https://web.dev/static/articles/mobile-touch/image/finger-tracking-c3a836001a408_48.png 48w,https://web.dev/static/articles/mobile-touch/image/finger-tracking-c3a836001a408_72.png 72w,https://web.dev/static/articles/mobile-touch/image/finger-tracking-c3a836001a408_96.png 96w,https://web.dev/static/articles/mobile-touch/image/finger-tracking-c3a836001a408_480.png 480w,https://web.dev/static/articles/mobile-touch/image/finger-tracking-c3a836001a408_720.png 720w,https://web.dev/static/articles/mobile-touch/image/finger-tracking-c3a836001a408_856.png 856w,https://web.dev/static/articles/mobile-touch/image/finger-tracking-c3a836001a408_960.png 960w,https://web.dev/static/articles/mobile-touch/image/finger-tracking-c3a836001a408_1440.png 1440w,https://web.dev/static/articles/mobile-touch/image/finger-tracking-c3a836001a408_1920.png 1920w,https://web.dev/static/articles/mobile-touch/image/finger-tracking-c3a836001a408_2880.png 2880w" sizes="(max-width: 840px) 100vw, 856px"> </a> </figure> <pre class="prettyprint lang-js" translate="no" dir="ltr"><code translate="no" dir="ltr">// Setup canvas and expose context via ctx variable canvas.addEventListener('touchmove', function(event) { for (var i = 0; i < event.touches.length; i++) { var touch = event.touches[i]; ctx.beginPath(); ctx.arc(touch.pageX, touch.pageY, 20, 0, 2*Math.PI, true); ctx.fill(); ctx.stroke(); } }, false); </code></pre> <h3 id="demos" data-text="Demos" tabindex="-1">Demos</h3> <p>A number of interesting multi-touch demos are already in the wild, such as this <a href="http://paulirish.com/demo/multi">canvas-based drawing</a> demo by Paul Irish and others.</p> <figure > <a href="http://paulirish.com/demo/multi"> <img src="/static/articles/mobile-touch/image/drawing-screenshot-bf38107d6f47a.png" alt="Drawing screenshot" width="650" height="244" srcset="https://web.dev/static/articles/mobile-touch/image/drawing-screenshot-bf38107d6f47a_36.png 36w,https://web.dev/static/articles/mobile-touch/image/drawing-screenshot-bf38107d6f47a_48.png 48w,https://web.dev/static/articles/mobile-touch/image/drawing-screenshot-bf38107d6f47a_72.png 72w,https://web.dev/static/articles/mobile-touch/image/drawing-screenshot-bf38107d6f47a_96.png 96w,https://web.dev/static/articles/mobile-touch/image/drawing-screenshot-bf38107d6f47a_480.png 480w,https://web.dev/static/articles/mobile-touch/image/drawing-screenshot-bf38107d6f47a_720.png 720w,https://web.dev/static/articles/mobile-touch/image/drawing-screenshot-bf38107d6f47a_856.png 856w,https://web.dev/static/articles/mobile-touch/image/drawing-screenshot-bf38107d6f47a_960.png 960w,https://web.dev/static/articles/mobile-touch/image/drawing-screenshot-bf38107d6f47a_1440.png 1440w,https://web.dev/static/articles/mobile-touch/image/drawing-screenshot-bf38107d6f47a_1920.png 1920w,https://web.dev/static/articles/mobile-touch/image/drawing-screenshot-bf38107d6f47a_2880.png 2880w" sizes="(max-width: 840px) 100vw, 856px"> </a> </figure> <p>And <a href="http://borismus.github.com/mobile-web-samples/browser-ninja/">Browser Ninja</a>, a tech demo that is a Fruit Ninja clone using CSS3 transforms and transitions, as well as canvas:</p> <figure > <a href="http://borismus.github.com/mobile-web-samples/browser-ninja/"> <img src="/static/articles/mobile-touch/image/browser-ninja-7dfdbcf47ac78.png" alt="Browser ninja" width="650" height="336" srcset="https://web.dev/static/articles/mobile-touch/image/browser-ninja-7dfdbcf47ac78_36.png 36w,https://web.dev/static/articles/mobile-touch/image/browser-ninja-7dfdbcf47ac78_48.png 48w,https://web.dev/static/articles/mobile-touch/image/browser-ninja-7dfdbcf47ac78_72.png 72w,https://web.dev/static/articles/mobile-touch/image/browser-ninja-7dfdbcf47ac78_96.png 96w,https://web.dev/static/articles/mobile-touch/image/browser-ninja-7dfdbcf47ac78_480.png 480w,https://web.dev/static/articles/mobile-touch/image/browser-ninja-7dfdbcf47ac78_720.png 720w,https://web.dev/static/articles/mobile-touch/image/browser-ninja-7dfdbcf47ac78_856.png 856w,https://web.dev/static/articles/mobile-touch/image/browser-ninja-7dfdbcf47ac78_960.png 960w,https://web.dev/static/articles/mobile-touch/image/browser-ninja-7dfdbcf47ac78_1440.png 1440w,https://web.dev/static/articles/mobile-touch/image/browser-ninja-7dfdbcf47ac78_1920.png 1920w,https://web.dev/static/articles/mobile-touch/image/browser-ninja-7dfdbcf47ac78_2880.png 2880w" sizes="(max-width: 840px) 100vw, 856px"> </a> </figure> <h2 id="best_practices" data-text="Best practices" tabindex="-1">Best practices</h2> <h3 id="prevent_zooming" data-text="Prevent zooming" tabindex="-1">Prevent zooming</h3> <p>Default settings don't work very well for multi-touch, since your swipes and gestures are often associated with browser behavior, such as scrolling and zooming.</p> <p>To disable zooming, setup your viewport so that it is not user scalable using the following meta tag:</p> <pre class="prettyprint lang-html" translate="no" dir="ltr"><code translate="no" dir="ltr"><meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no> </code></pre> <p>Check out <a href="https://www.html5rocks.com/mobile/mobifying.html#toc-meta-viewport">this mobile HTML5 article</a> for more information on setting your viewport.</p> <h3 id="prevent_scrolling" data-text="Prevent scrolling" tabindex="-1">Prevent scrolling</h3> <p>Some mobile devices have default behaviors for touchmove, such as the classic iOS overscroll effect, which causes the view to bounce back when scrolling exceeds the bounds of the content. This is confusing in many multi-touch applications, and can easily be disabled:</p> <pre class="prettyprint lang-js" translate="no" dir="ltr"><code translate="no" dir="ltr">document.body.addEventListener('touchmove', function(event) { event.preventDefault(); }, false); </code></pre> <h3 id="render_carefully" data-text="Render carefully" tabindex="-1">Render carefully</h3> <p>If you are writing a multi-touch application that involves complex multi-finger gestures, be careful how you react to touch events, since you will be handling so many at once. Consider the sample in the previous section that draws all touches on the screen. You could draw as soon as there is a touch input:</p> <pre class="prettyprint lang-js" translate="no" dir="ltr"><code translate="no" dir="ltr">canvas.addEventListener('touchmove', function(event) { renderTouches(event.touches); }, false); </code></pre> <p>But this technique does not scale with number of fingers on the screen. Instead, you could track all of the fingers, and render in a loop to get far better performance:</p> <pre class="prettyprint lang-js" translate="no" dir="ltr"><code translate="no" dir="ltr">var touches = [] canvas.addEventListener('touchmove', function(event) { touches = event.touches; }, false); // Setup a 60fps timer timer = setInterval(function() { renderTouches(touches); }, 15); </code></pre> <aside class="note"><b>Note: </b> <strong>Tip:</strong> setInterval is not great for animations, since it doesn't take into account the browser's own rendering loop. Modern desktop browsers provide <a href="https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame">requestAnimationFrame</a>, which is a much better option for performance and battery life reasons. Once supported in mobile browsers, this will be the preferred way of doing things. </aside> <h3 id="make_use_of_targettouches_and_changedtouches" data-text="Make use of targetTouches and changedTouches" tabindex="-1">Make use of targetTouches and changedTouches</h3> <p>Remember that event.touches is an array of <strong>ALL</strong> fingers in contact with the screen, not just the ones on the DOM element's target. You might find it much more useful to use event.targetTouches or event.changedTouches instead.</p> <p>Finally, since you are developing for mobile, you should be aware of general mobile best practices, which are covered in <a href="https://www.html5rocks.com/mobile/mobifying.html">Eric Bidelman's article</a>, as well as this <a href="http://www.w3.org/TR/mwabp/">W3C document</a>.</p> <h2 id="device_support" data-text="Device support" tabindex="-1">Device support</h2> <p>Unfortunately, touch event implementations vary greatly in completeness and quality. I wrote a <a href="https://github.com/borismus/MagicTouch/blob/master/index.html">diagnostics script</a> that displays some basic information about the touch API implementation, including which events are supported, and touchmove firing resolution. I tested Android 2.3.3 on Nexus One and Nexus S hardware, Android 3.0.1 on Xoom, and iOS 4.2 on iPad and iPhone.</p> <p>In a nutshell, all tested browsers support the <strong>touchstart</strong>, <strong>touchend</strong>, and <strong>touchmove</strong> events. </p> <p>The spec provides three additional touch events, but no tested browsers support them: </p> <ul> <li><strong>touchenter</strong>: a moving finger enters a DOM element.</li> <li><strong>touchleave</strong>: a moving finger leaves a DOM element.</li> <li><strong>touchcancel</strong>: a touch is interrupted (implementation specific).</li> </ul> <p>Within each touch list, the tested browsers also provide the <strong>touches</strong>, <strong>targetTouches</strong> and <strong>changedTouches</strong> touch lists. However, no tested browsers support radiusX, radiusY or rotationAngle, which specify the shape of the finger touching the screen.</p> <p>During a touchmove, events fire at roughly 60 times a second across all tested devices.</p> <h3 id="android_233_nexus" data-text="Android 2.3.3 (Nexus)" tabindex="-1">Android 2.3.3 (Nexus)</h3> <p>On the Android Gingerbread Browser (tested on Nexus One and Nexus S), there is no multi-touch support. This is a <a href="http://code.google.com/p/android/issues/detail?id=11909">known issue</a>.</p> <h3 id="android_301_xoom" data-text="Android 3.0.1 (Xoom)" tabindex="-1">Android 3.0.1 (Xoom)</h3> <p>On Xoom's browser, there is basic multi-touch support, but it only works on a single DOM element. The browser does not correctly respond to two simultaneous touches on different DOM elements. In other words, the following will react to two simultaneous touches:</p> <pre class="prettyprint lang-js" translate="no" dir="ltr"><code translate="no" dir="ltr">obj1.addEventListener('touchmove', function(event) { for (var i = 0; i < event.targetTouches; i++) { var touch = event.targetTouches[i]; console.log('touched ' + touch.identifier); } }, false); </code></pre> <p>But the following will not:</p> <pre class="prettyprint lang-js" translate="no" dir="ltr"><code translate="no" dir="ltr">var objs = [obj1, obj2]; for (var i = 0; i < objs.length; i++) { var obj = objs[i]; obj.addEventListener('touchmove', function(event) { if (event.targetTouches.length == 1) { console.log('touched ' + event.targetTouches[0].identifier); } }, false); } </code></pre> <h3 id="ios_4x_ipad_iphone" data-text="iOS 4.x (iPad, iPhone)" tabindex="-1">iOS 4.x (iPad, iPhone)</h3> <p>iOS devices fully support multi-touch, are capable of tracking quite a few fingers and provide a very responsive touch experience in the browser.</p> <h2 id="developer_tools" data-text="Developer tools" tabindex="-1">Developer tools</h2> <p>In mobile development, it's often easier to start prototyping on the desktop and then tackle the mobile-specific parts on the devices you intend to support. Multi-touch is one of those features that's difficult to test on the PC, since most PCs don't have touch input.</p> <p>Having to test on mobile can lengthen your development cycle, since every change you make needs to be pushed out to a server and then loaded on the device. Then, once running, there’s little you can do to debug your application, since tablets and smartphones lack web developer tooling.</p> <p>A solution to this problem is to simulate touch events on your development machine. For single-touches, touch events can be simulated based on mouse events. Multi-touch events can be simulated if you have a device with touch input, such as a modern Apple MacBook.</p> <h3 id="single-touch_events" data-text="Single-touch events" tabindex="-1">Single-touch events</h3> <p>If you would like to simulate single-touch events on your desktop, Chrome provides touch event emulation from the developer tools. Open up the Developer tools, then select the Settings gear, then "Overrides" or "Emulation", and turn on "Emulate touch events".</p> <p>For other browsers, you may wish to try out <a href="http://www.vodori.com/blog/phantom-limb.html">Phantom Limb</a>, which simulates touch events on pages and also gives a giant hand to boot.</p> <p>There's also the <a href="https://github.com/dotmaster/Touchable-jQuery-Plugin">Touchable</a> jQuery plugin that unifies touch and mouse events across platforms.</p> <h3 id="multi-touch_events" data-text="Multi-touch events" tabindex="-1">Multi-touch events</h3> <p>To enable your multi-touch web application to work in your browser on your multi-touch trackpad (such as a Apple MacBook or MagicPad), I've created the <a href="http://github.com/borismus/MagicTouch">MagicTouch.js polyfill</a>. It captures touch events from your trackpad and turns them into standard-compatible touch events.</p> <ol> <li>Download and install the <a href="https://github.com/fajran/npTuioClient">npTuioClient NPAPI plugin</a> into ~/Library/Internet Plug-Ins/.</li> <li>Download the <a href="https://github.com/fajran/tongseng">TongSeng TUIO app</a> for Mac’s MagicPad and start the server.</li> <li>Download <a href="http://github.com/borismus/MagicTouch">MagicTouch.js</a>, a javascript library to simulate spec-compatible touch events based on npTuioClient callbacks.</li> <li>Include the magictouch.js script and npTuioClient plugin in your application as follows:</li> </ol> <pre class="prettyprint lang-html" translate="no" dir="ltr"><code translate="no" dir="ltr"><head> ... <script src="/path/to/magictouch.js"></script> </head> <body> ... <object id="tuio" type="application/x-tuio" style="width: 0px; height: 0px;"> Touch input plugin failed to load! </object> </body> </code></pre> <p>You may need to enable the plugin.</p> <p>A live demo with magictouch.js is available at <a href="http://www.paulirish.com/demo/multi">paulirish.com/demo/multi</a>:</p> <p><devsite-video video-id="6wL-pLX9Kq0"></devsite-video></p> <p>I tested this approach only with Chrome 10, but it should work on other modern browsers with only minor tweaks.</p> <p>If your computer does not have multi-touch input, you can simulate touch events using other TUIO trackers, such as the <a href="http://reactivision.sourceforge.net/">reacTIVision</a>. For more information, see the <a href="http://www.tuio.org/">TUIO project page</a>.</p> <p>Note that your gestures might be identical to OS-level multi-touch gestures. On OS X, you can configure system-wide events by going to the Trackpad preference pane in System Preferences.</p> <p>As multi-touch features become more widely supported across mobile browsers, I'm very excited to see new web applications take full advantage of this rich API.</p> </div> <devsite-thumb-rating position="footer"> </devsite-thumb-rating> <div class="devsite-floating-action-buttons"> </div> </article> <devsite-content-footer class="nocontent"> <p>Except as otherwise noted, the content of this page is licensed under the <a href="https://creativecommons.org/licenses/by/4.0/">Creative Commons Attribution 4.0 License</a>, and code samples are licensed under the <a href="https://www.apache.org/licenses/LICENSE-2.0">Apache 2.0 License</a>. For details, see the <a href="https://developers.google.com/site-policies">Google Developers Site Policies</a>. Java is a registered trademark of Oracle and/or its affiliates.</p> <p>Last updated 2011-08-21 UTC.</p> </devsite-content-footer> <devsite-notification > </devsite-notification> <div class="devsite-content-data"> <template class="devsite-content-data-template"> [[["Easy to understand","easyToUnderstand","thumb-up"],["Solved my problem","solvedMyProblem","thumb-up"],["Other","otherUp","thumb-up"]],[["Missing the information I need","missingTheInformationINeed","thumb-down"],["Too complicated / too many steps","tooComplicatedTooManySteps","thumb-down"],["Out of date","outOfDate","thumb-down"],["Samples / code issue","samplesCodeIssue","thumb-down"],["Other","otherDown","thumb-down"]],["Last updated 2011-08-21 UTC."],[],[]] </template> </div> </devsite-content> </main> <devsite-footer-promos class="devsite-footer"> </devsite-footer-promos> <devsite-footer-linkboxes class="devsite-footer"> <nav class="devsite-footer-linkboxes nocontent" aria-label="Footer links"> <ul class="devsite-footer-linkboxes-list"> <li class="devsite-footer-linkbox wd-footer-promo"> <h3 class="devsite-footer-linkbox-heading no-link">web.dev</h3> <ul class="devsite-footer-linkbox-list"> <li class="devsite-footer-linkbox-item"> <h3 class="devsite-footer-linkbox-heading no-link"> web.dev </h3> <div class="devsite-footer-linkbox-description">We want to help you build beautiful, accessible, fast, and secure websites that work cross-browser, and for all of your users. This site is our home for content to help you on that journey, written by members of the Chrome team, and external experts.</div> </li> </ul> </li> <li class="devsite-footer-linkbox "> <h3 class="devsite-footer-linkbox-heading no-link">Contribute</h3> <ul class="devsite-footer-linkbox-list"> <li class="devsite-footer-linkbox-item"> <a href="https://issuetracker.google.com/issues/new?component=1400680&template=1857359" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 1)" > File a bug </a> </li> <li class="devsite-footer-linkbox-item"> <a href="https://issuetracker.google.com/issues?q=status:open%20componentid:1400680&s=created_time:desc" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 2)" > See open issues </a> </li> </ul> </li> <li class="devsite-footer-linkbox "> <h3 class="devsite-footer-linkbox-heading no-link">Related Content</h3> <ul class="devsite-footer-linkbox-list"> <li class="devsite-footer-linkbox-item"> <a href="https://developer.chrome.com/" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 1)" > Chrome for Developers </a> </li> <li class="devsite-footer-linkbox-item"> <a href="https://blog.chromium.org/" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 2)" > Chromium updates </a> </li> <li class="devsite-footer-linkbox-item"> <a href="/case-studies" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 3)" > Case studies </a> </li> <li class="devsite-footer-linkbox-item"> <a href="/shows" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 4)" > Podcasts & shows </a> </li> </ul> </li> <li class="devsite-footer-linkbox "> <h3 class="devsite-footer-linkbox-heading no-link">Follow</h3> <ul class="devsite-footer-linkbox-list"> <li class="devsite-footer-linkbox-item"> <a href="https://twitter.com/ChromiumDev" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 1)" > @ChromiumDev on X </a> </li> <li class="devsite-footer-linkbox-item"> <a href="https://www.youtube.com/user/ChromeDevelopers" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 2)" > YouTube </a> </li> <li class="devsite-footer-linkbox-item"> <a href="https://www.linkedin.com/showcase/chrome-for-developers" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 3)" > Chrome for Developers on LinkedIn </a> </li> <li class="devsite-footer-linkbox-item"> <a href="/static/blog/feed.xml" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 4)" > RSS </a> </li> </ul> </li> </ul> </nav> </devsite-footer-linkboxes> <devsite-footer-utility class="devsite-footer"> <div class="devsite-footer-utility nocontent"> <nav class="devsite-footer-utility-links" aria-label="Utility links"> <ul class="devsite-footer-utility-list"> <li class="devsite-footer-utility-item "> <a class="devsite-footer-utility-link gc-analytics-event" href="//policies.google.com/terms" data-category="Site-Wide Custom Events" data-label="Footer Terms link" > Terms </a> </li> <li class="devsite-footer-utility-item "> <a class="devsite-footer-utility-link gc-analytics-event" href="//policies.google.com/privacy" data-category="Site-Wide Custom Events" data-label="Footer Privacy link" > Privacy </a> </li> <li class="devsite-footer-utility-item glue-cookie-notification-bar-control"> <a class="devsite-footer-utility-link gc-analytics-event" href="#" data-category="Site-Wide Custom Events" data-label="Footer Manage cookies link" aria-hidden="true" > Manage cookies </a> </li> </ul> <devsite-language-selector> <ul role="presentation"> <li role="presentation"> <a role="menuitem" lang="en" >English</a> </li> <li role="presentation"> <a role="menuitem" lang="de" >Deutsch</a> </li> <li role="presentation"> <a role="menuitem" lang="es_419" >Español – América Latina</a> </li> <li role="presentation"> <a role="menuitem" lang="fr" >Français</a> </li> <li role="presentation"> <a role="menuitem" lang="id" >Indonesia</a> </li> <li role="presentation"> <a role="menuitem" lang="it" >Italiano</a> </li> <li role="presentation"> <a role="menuitem" lang="pl" >Polski</a> </li> <li role="presentation"> <a role="menuitem" lang="pt_br" >Português – Brasil</a> </li> <li role="presentation"> <a role="menuitem" lang="vi" >Tiếng Việt</a> </li> <li role="presentation"> <a role="menuitem" lang="tr" >Türkçe</a> </li> <li role="presentation"> <a role="menuitem" lang="ru" >Русский</a> </li> <li role="presentation"> <a role="menuitem" lang="he" >עברית</a> </li> <li role="presentation"> <a role="menuitem" lang="ar" >العربيّة</a> </li> <li role="presentation"> <a role="menuitem" lang="fa" >فارسی</a> </li> <li role="presentation"> <a role="menuitem" lang="hi" >हिंदी</a> </li> <li role="presentation"> <a role="menuitem" lang="bn" >বাংলা</a> </li> <li role="presentation"> <a role="menuitem" lang="th" >ภาษาไทย</a> </li> <li role="presentation"> <a role="menuitem" lang="zh_cn" >中文 – 简体</a> </li> <li role="presentation"> <a role="menuitem" lang="zh_tw" >中文 – 繁體</a> </li> <li role="presentation"> <a role="menuitem" lang="ja" >日本語</a> </li> <li role="presentation"> <a role="menuitem" lang="ko" >한국어</a> </li> </ul> </devsite-language-selector> </nav> </div> </devsite-footer-utility> <devsite-panel></devsite-panel> </section></section> <devsite-sitemask></devsite-sitemask> <devsite-snackbar></devsite-snackbar> <devsite-tooltip ></devsite-tooltip> <devsite-heading-link></devsite-heading-link> <devsite-analytics> <script type="application/json" analytics>[]</script> <script type="application/json" tag-management>{"at": "True", "ga4": [], "ga4p": [], "gtm": [{"id": "GTM-MZWCJPP", "purpose": 0}], "parameters": {"internalUser": "False", "language": {"machineTranslated": "False", "requested": "en", "served": "en"}, "pageType": "article", "projectName": "Articles", "signedIn": "False", "tenant": "web", "recommendations": {"sourcePage": "", "sourceType": 0, "sourceRank": 0, "sourceIdenticalDescriptions": 0, "sourceTitleWords": 0, "sourceDescriptionWords": 0, "experiment": ""}, "experiment": {"ids": ""}}}</script> </devsite-analytics> <devsite-badger></devsite-badger> <script nonce="MsWQ2ndVFnixZmBU4I6I3L0w1qnU46"> (function(d,e,v,s,i,t,E){d['GoogleDevelopersObject']=i; t=e.createElement(v);t.async=1;t.src=s;E=e.getElementsByTagName(v)[0]; E.parentNode.insertBefore(t,E);})(window, document, 'script', 'https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/web/js/app_loader.js', '[27,"en",null,"/js/devsite_app_module.js","https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625","https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/web","https://web-dot-devsite-v2-prod-3p.appspot.com",1,null,["/_pwa/web/manifest.json","https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/images/video-placeholder.svg","https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/web/images/favicon.png","https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/web/images/lockup.svg","https://fonts.googleapis.com/css?family=Google+Sans:400,500|Roboto:400,400italic,500,500italic,700,700italic|Roboto+Mono:400,500,700&display=swap"],1,null,[1,6,8,12,14,17,21,25,50,52,63,70,75,76,80,87,91,92,93,97,98,100,101,102,103,104,105,107,108,109,110,112,113,117,118,120,122,124,125,126,127,129,130,131,132,133,134,135,136,138,140,141,147,148,149,151,152,156,157,158,159,161,163,164,168,169,170,179,180,182,183,186,191,193,196],"AIzaSyCNm9YxQumEXwGJgTDjxoxXK6m1F-9720Q","AIzaSyCc76DZePGtoyUjqKrLdsMGk_ry7sljLbY","web.dev","AIzaSyB9bqgQ2t11WJsOX8qNsCQ6U-w91mmqF-I","AIzaSyAdYnStPdzjcJJtQ0mvIaeaMKj7_t6J_Fg",null,null,null,["Search__enable_suggestions_from_borg","Profiles__enable_page_saving","Cloud__enable_legacy_calculator_redirect","Experiments__reqs_query_experiments","DevPro__enable_developer_subscriptions","MiscFeatureFlags__emergency_css","Profiles__enable_dashboard_curated_recommendations","Profiles__enable_developer_profiles_callout","CloudShell__cloud_shell_button","Profiles__enable_completecodelab_endpoint","Cloud__enable_cloudx_experiment_ids","Concierge__enable_pushui","Search__enable_page_map","MiscFeatureFlags__enable_project_variables","Cloud__enable_cloud_facet_chat","Analytics__enable_clearcut_logging","MiscFeatureFlags__developers_footer_image","DevPro__enable_cloud_innovators_plus","Cloud__enable_cloud_dlp_service","MiscFeatureFlags__enable_variable_operator","TpcFeatures__enable_required_headers","MiscFeatureFlags__developers_footer_dark_image","EngEduTelemetry__enable_engedu_telemetry","Search__enable_ai_eligibility_checks","Cloud__enable_cloud_shell","Profiles__enable_awarding_url","OnSwitch__enable","Profiles__enable_complete_playlist_endpoint","Profiles__enable_recognition_badges","Profiles__enable_release_notes_notifications","Cloud__enable_cloud_shell_fte_user_flow","Cloud__enable_free_trial_server_call","Cloud__enable_llm_concierge_chat","BookNav__enable_tenant_cache_key","Profiles__require_profile_eligibility_for_signin","CloudShell__cloud_code_overflow_menu","MiscFeatureFlags__enable_explain_this_code","MiscFeatureFlags__enable_view_transitions","TpcFeatures__enable_mirror_tenant_redirects","MiscFeatureFlags__enable_firebase_utm","Profiles__enable_profile_collections","Cloud__enable_cloudx_ping","Profiles__enable_public_developer_profiles","Search__enable_dynamic_content_confidential_banner"],null,null,"AIzaSyA58TaKli1DculwmAmbpzLVGuWc8eCQgQc","https://developerscontentserving-pa.googleapis.com","AIzaSyDWBU60w0P9hEkr29kkksYs8Z7gvZ8u_wc","https://developerscontentsearch-pa.googleapis.com",2,4,null,"https://developerprofiles-pa.googleapis.com",[27,"web","web.dev","web.dev",null,"web-dot-devsite-v2-prod-3p.appspot.com",null,null,[null,null,null,null,null,null,null,null,null,null,null,[1],null,null,null,null,null,null,[1],null,null,null,null,[1,null,1],[1,1,null,1,1]],null,[38,null,null,null,null,null,"/images/lockup.svg","/images/touchicon-180.png",null,null,null,1,1,null,null,null,null,null,null,null,null,2,null,null,null,"/images/lockup-dark-theme.svg",[]],[],null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,[[],[1,1]],[[null,null,null,null,null,["GTM-MZWCJPP"],null,null,null,null,null,[["GTM-MZWCJPP",1]],1]],null,4]]') </script> <devsite-a11y-announce></devsite-a11y-announce> </body> </html>