با کروم بسازید

آوردن آجرهای LEGO® به وب چند دستگاهی

Build with Chrome، آزمایشی سرگرم‌کننده برای کاربران دسک‌تاپ کروم که در ابتدا در استرالیا راه‌اندازی شد ، در سال ۲۰۱۴ مجدداً منتشر شد و در دسترس‌پذیری جهانی، پیوندهایی با THE LEGO® MOVIE™ و پشتیبانی جدید اضافه‌شده برای دستگاه‌های تلفن همراه بود. در این مقاله ما برخی از آموخته‌های این پروژه را به اشتراک می‌گذاریم، به خصوص در مورد حرکت از تجربه فقط دسکتاپ به یک راه‌حل چند صفحه‌نمایش که از ورودی ماوس و لمسی پشتیبانی می‌کند.

تاریخچه ساخت با کروم

اولین نسخه Build with Chrome در سال 2012 در استرالیا راه اندازی شد. ما می خواستیم قدرت وب را به روشی کاملاً جدید نشان دهیم و Chrome را به مخاطبان کاملاً جدیدی ارائه دهیم.

این سایت دارای دو بخش اصلی بود: حالت «ساخت» که در آن کاربران می‌توانند با استفاده از آجرهای لگو آثاری بسازند، و حالت «کاوش» برای مرور آثار در نسخه‌ای از Google Maps که توسط لگو ساخته شده است.

سه بعدی تعاملی برای ارائه بهترین تجربه ساخت لگو به کاربران ضروری بود. در سال 2012، WebGL فقط در مرورگرهای دسکتاپ به صورت عمومی در دسترس بود، بنابراین Build به عنوان یک تجربه فقط دسکتاپ مورد هدف قرار گرفت. Google Maps استفاده شده را برای نمایش آثار کاوش کنید، اما زمانی که به اندازه کافی نزدیک‌تر می‌شوید، به اجرای WebGL نقشه تغییر می‌کند که آثار را به صورت سه بعدی نشان می‌دهد، همچنان از نقشه‌های Google به عنوان بافت پایه استفاده می‌کند. ما امیدوار بودیم که محیطی بسازیم که در آن علاقه مندان به لگو در هر سنی بتوانند به راحتی و به طور شهودی خلاقیت خود را بروز دهند و خلاقیت های یکدیگر را کشف کنند.

در سال 2013، تصمیم گرفتیم Build with Chrome را به فناوری‌های جدید وب گسترش دهیم. یکی از این فناوری‌ها WebGL در کروم برای اندروید بود که طبیعتاً به Build with Chrome اجازه می‌دهد تا به یک تجربه تلفن همراه تبدیل شود. برای شروع، ابتدا نمونه‌های اولیه لمسی را توسعه دادیم، قبل از اینکه سخت‌افزار «ابزار سازنده» را زیر سوال ببریم تا رفتار حرکتی و واکنش لمسی را که ممکن است از طریق مرورگر در مقایسه با یک برنامه تلفن همراه با آن مواجه شویم، درک کنیم.

یک Front-end پاسخگو

ما نیاز به پشتیبانی از دستگاه ها با ورودی لمسی و ماوس داشتیم. با این حال، استفاده از رابط کاربری مشابه در صفحه نمایش های لمسی کوچک به دلیل محدودیت فضا، راه حلی غیربهینه بود.

در Build تعاملات زیادی وجود دارد: بزرگنمایی و کوچک‌نمایی، تغییر رنگ‌های آجری، و البته انتخاب، چرخش و قرار دادن آجرها. این ابزاری است که کاربران اغلب زمان زیادی را در آن صرف می‌کنند، بنابراین مهم است که به هر چیزی که اغلب استفاده می‌کنند دسترسی سریع داشته باشند و در تعامل با آن احساس راحتی کنند.

هنگام طراحی یک برنامه لمسی بسیار تعاملی، متوجه خواهید شد که صفحه نمایش به سرعت کوچک به نظر می رسد و انگشتان کاربر تمایل دارند قسمت زیادی از صفحه را در حین تعامل پوشش دهند. این برای ما هنگام کار با سازنده آشکار شد. هنگام طراحی، واقعاً باید اندازه فیزیکی صفحه نمایش را به جای پیکسل های گرافیکی در نظر بگیرید. به حداقل رساندن تعداد دکمه‌ها و کنترل‌ها برای به دست آوردن هرچه بیشتر صفحه نمایش واقعی به محتوای واقعی اهمیت می‌یابد.

هدف ما ایجاد حس طبیعی در Build در دستگاه‌های لمسی بود، نه تنها افزودن ورودی لمسی به پیاده‌سازی دسک‌تاپ اصلی، بلکه ایجاد حسی که واقعاً برای لمس در نظر گرفته شده بود. ما در نهایت به دو نوع رابط کاربری رسیدیم، یکی برای دسکتاپ و تبلت با صفحه نمایش بزرگ و دیگری برای دستگاه های تلفن همراه با صفحه نمایش کوچکتر. در صورت امکان، بهتر است از یک پیاده سازی استفاده کنید و یک انتقال سیال بین حالت ها داشته باشید. در مورد ما، ما تشخیص دادیم که تفاوت قابل توجهی در تجربه بین این دو حالت وجود دارد که تصمیم گرفتیم به یک نقطه شکست خاص تکیه کنیم. این دو نسخه دارای ویژگی‌های مشترک زیادی هستند و ما سعی کردیم بیشتر کارها را تنها با یک کد پیاده‌سازی انجام دهیم، اما برخی از جنبه‌های رابط کاربری بین این دو متفاوت است.

ما از داده‌های عامل کاربر برای شناسایی دستگاه‌های تلفن همراه استفاده می‌کنیم و سپس اندازه درگاه دید را بررسی می‌کنیم تا تصمیم بگیریم که آیا رابط کاربری تلفن همراه با صفحه نمایش کوچک باید استفاده شود یا خیر. انتخاب یک نقطه شکست برای آنچه که یک صفحه نمایش بزرگ باید باشد کمی سخت است، زیرا بدست آوردن یک مقدار قابل اعتماد از اندازه فیزیکی صفحه سخت است. خوشبختانه، در مورد ما، واقعاً مهم نیست که رابط کاربری صفحه کوچک را روی یک دستگاه لمسی با صفحه نمایش بزرگ نشان دهیم، زیرا این ابزار همچنان خوب کار می کند، فقط ممکن است برخی از دکمه ها کمی بیش از حد بزرگ به نظر برسند. در پایان نقطه شکست را روی 1000 پیکسل قرار می دهیم. اگر سایت را از پنجره ای با پهنای بیش از 1000 پیکسل بارگذاری کنید (در حالت افقی)، نسخه صفحه نمایش بزرگ را دریافت خواهید کرد.

بیایید کمی در مورد دو اندازه صفحه نمایش و تجربه صحبت کنیم:

صفحه نمایش بزرگ، با پشتیبانی از ماوس و لمسی

نسخه صفحه نمایش بزرگ برای همه رایانه های رومیزی با پشتیبانی از ماوس و دستگاه های لمسی با صفحه نمایش بزرگ (مانند Google Nexus 10) ارائه می شود. این نسخه نزدیک به راه حل اصلی دسکتاپ در نوع کنترل های ناوبری موجود است، اما ما پشتیبانی لمسی و برخی حرکات را اضافه کردیم. ما UI را بسته به اندازه پنجره تنظیم می کنیم، بنابراین وقتی کاربر اندازه پنجره را تغییر می دهد، ممکن است برخی از UI را حذف یا تغییر اندازه دهد. ما این کار را با استفاده از پرس و جوهای رسانه CSS انجام می دهیم.

مثال: وقتی ارتفاع موجود کمتر از 730 پیکسل باشد، کنترل لغزنده زوم در حالت کاوش پنهان می‌شود:

@media only screen and (max-height: 730px) {
    .zoom-slider {
        display: none;
    }
}

صفحه نمایش کوچک، فقط پشتیبانی لمسی

این نسخه برای دستگاه های تلفن همراه و تبلت های کوچک (دستگاه های هدف Nexus 4 و Nexus 7) ارائه می شود. این نسخه نیاز به پشتیبانی چند لمسی دارد.

در دستگاه‌های صفحه‌نمایش کوچک، ما باید تا حد امکان به محتوای صفحه نمایش بدهیم، بنابراین برای به حداکثر رساندن فضا، تغییراتی را انجام دادیم، عمدتاً با جابه‌جایی عناصری که به ندرت استفاده می‌شوند خارج از دید:

  • انتخابگر Build brick در حین ساخت به یک انتخابگر رنگ کوچک می شود.
  • ما کنترل های بزرگنمایی و جهت یابی را با حرکات چند لمسی جایگزین کردیم.
  • عملکرد تمام صفحه کروم نیز برای به دست آوردن صفحه نمایش اضافی مفید است.
روی یک صفحه نمایش بزرگ بسازید
روی یک صفحه نمایش بزرگ بسازید. انتخابگر آجر همیشه قابل مشاهده است و چند کنترل در سمت راست وجود دارد.
روی یک صفحه نمایش کوچک بسازید
روی یک صفحه نمایش کوچک بسازید. انتخابگر آجر به حداقل رسیده است و برخی از دکمه ها حذف شده اند.

عملکرد و پشتیبانی WebGL

دستگاه‌های لمسی مدرن دارای پردازنده‌های گرافیکی کاملاً قدرتمندی هستند، اما هنوز از همتایان دسکتاپ خود فاصله دارند، بنابراین می‌دانستیم که در عملکرد با چالش‌هایی روبرو خواهیم بود، به خصوص در حالت Explore 3D که در آن باید تعداد زیادی خلاقیت را همزمان ارائه کنیم.

به طور خلاقانه، ما می‌خواستیم چند نوع جدید آجر با اشکال پیچیده و حتی شفاف اضافه کنیم -- ویژگی‌هایی که معمولاً برای GPU بسیار سنگین هستند. با این حال، ما مجبور بودیم با نسخه‌های قبلی سازگاری داشته باشیم و به حمایت از خلاقیت‌های نسخه اول ادامه دهیم، بنابراین نمی‌توانیم محدودیت‌های جدیدی مانند کاهش چشمگیر تعداد کل آجرها در آثار ایجاد کنیم.

در نسخه اول Build ما حداکثر محدودیت آجری داشتیم که می‌توانستند در یک ساخت استفاده شوند. یک "آجر متر" وجود داشت که نشان می داد چند آجر باقی مانده است. در اجرای جدید، ما تعدادی از آجرهای جدید را داشتیم که بیشتر از آجرهای استاندارد، آجر متر را تحت تأثیر قرار می‌دادند، در نتیجه حداکثر تعداد کل آجرها را اندکی کاهش دادیم. این یکی از راه‌های گنجاندن آجرهای جدید با حفظ عملکرد مناسب بود.

در حالت کاوش سه بعدی، اتفاقات زیادی در همان زمان رخ می دهد. بارگذاری بافت های بیس پلیت، بارگذاری خلاقیت ها، انیمیشن سازی و رندر کردن خلاقیت ها و غیره. این کار به مقدار زیادی از GPU و CPU نیاز دارد، بنابراین ما در Chrome DevTools نمایه فریم های زیادی را انجام دادیم تا این بخش ها را تا حد امکان بهینه کنیم. در دستگاه‌های تلفن همراه تصمیم گرفتیم کمی به آثار نزدیک‌تر زوم کنیم تا مجبور نباشیم همزمان چندین اثر را ارائه دهیم.

برخی از دستگاه‌ها از ما خواستند که برخی از سایه‌زن‌های WebGL را دوباره بررسی کرده و ساده کنیم، اما ما همیشه راهی برای حل آن و حرکت به جلو پیدا می‌کردیم.

پشتیبانی از دستگاه های غیر WebGL

ما می خواستیم سایت تا حدودی قابل استفاده باشد حتی اگر دستگاه بازدید کننده از WebGL پشتیبانی نمی کند. گاهی اوقات راه هایی برای نمایش سه بعدی به روشی ساده با استفاده از راه حل بوم یا ویژگی های CSS3D وجود دارد. متأسفانه ما راه حل کافی برای تکرار ویژگی های ساخت و کاوش سه بعدی بدون استفاده از WebGL پیدا نکردیم.

برای ثبات، سبک بصری خلاقیت ها باید در همه پلتفرم ها یکسان باشد. ما به طور بالقوه می توانستیم برای یک راه حل 2.5 بعدی تلاش کنیم، اما این باعث می شود که خلاقیت ها از جهاتی متفاوت به نظر برسند. ما همچنین باید در نظر می گرفتیم که چگونه مطمئن شویم آثار ساخته شده با اولین نسخه Build with Chrome یکسان به نظر می رسند و در نسخه جدید سایت به همان راحتی اجرا می شوند که در نسخه اول انجام شد.

حالت کاوش دو بعدی همچنان برای دستگاه‌های غیر WebGL قابل دسترسی است، حتی اگر نمی‌توانید آثار جدید بسازید یا به صورت سه بعدی کاوش کنید. بنابراین کاربران هنوز هم می توانند ایده ای از عمق پروژه و آنچه می توانند با استفاده از این ابزار ایجاد کنند، اگر در یک دستگاه دارای WebGL باشند، داشته باشند. این سایت ممکن است بدون پشتیبانی WebGL برای کاربران ارزشمند نباشد، اما حداقل باید به عنوان یک تیزر عمل کند و آنها را در آزمایش آن مشارکت دهد.

نگه داشتن نسخه های بازگشتی برای راه حل های WebGL گاهی اوقات ممکن نیست. دلایل زیادی وجود دارد؛ عملکرد، سبک بصری، هزینه های توسعه و نگهداری و غیره. با این حال، زمانی که تصمیم می‌گیرید یک نسخه بازگشتی را پیاده‌سازی نکنید، حداقل باید مراقب بازدیدکنندگان غیرفعال از WebGL باشید، توضیح دهید که چرا آنها نمی‌توانند به طور کامل به سایت دسترسی پیدا کنند، و دستورالعمل‌هایی برای حل مشکل با استفاده از یک مرورگری که از WebGL پشتیبانی می کند.

مدیریت دارایی

در سال 2013، گوگل نسخه جدیدی از Google Maps را با مهمترین تغییرات رابط کاربری از زمان راه اندازی آن معرفی کرد. بنابراین ما تصمیم گرفتیم Build را با کروم دوباره طراحی کنیم تا با رابط کاربری جدید Google Maps مطابقت داشته باشد و در این کار عوامل دیگری را در طراحی مجدد در نظر گرفتیم. طرح جدید نسبتاً مسطح با رنگ‌های جامد تمیز و اشکال ساده است. این ما را قادر می سازد از CSS خالص در بسیاری از عناصر UI استفاده کنیم و استفاده از تصاویر را به حداقل برسانیم.

در Explore باید تصاویر زیادی را بارگذاری کنیم. تصاویر کوچک برای خلاقیت ها، بافت های نقشه برای صفحات پایه و در نهایت خلاقیت های سه بعدی واقعی. هنگامی که دائماً تصاویر جدید را بارگذاری می‌کنیم، مراقبت بیشتری می‌کنیم تا مطمئن شویم که حافظه‌ای نشتی نداشته باشد.

آثار سه بعدی در قالب فایل سفارشی بسته بندی شده به عنوان تصویر PNG ذخیره می شوند. ذخیره سازی داده های سه بعدی به عنوان یک تصویر به ما امکان می دهد که اساساً داده ها را مستقیماً به سایه بان هایی که آثار را ارائه می دهند منتقل کنیم.

برای همه تصاویر تولید شده توسط کاربر، طراحی به ما اجازه می‌دهد تا از اندازه‌های یکسان تصویر برای همه پلتفرم‌ها استفاده کنیم، بنابراین استفاده از ذخیره‌سازی و پهنای باند را به حداقل می‌رسانیم.

مدیریت جهت گیری صفحه نمایش

به راحتی می توان فراموش کرد که نسبت ابعاد صفحه هنگام حرکت از حالت عمودی به حالت افقی یا برعکس، چقدر تغییر می کند. هنگام تطبیق با دستگاه های تلفن همراه، باید این را از ابتدا در نظر بگیرید.

در یک وب‌سایت سنتی که اسکرول فعال است، می‌توانید قوانین CSS را اعمال کنید تا یک سایت واکنش‌گرا دریافت کنید که محتوا و منوها را مجدداً مرتب می‌کند. تا زمانی که می توانید از قابلیت اسکرول استفاده کنید، این نسبتاً قابل مدیریت است.

ما از این روش با Build نیز استفاده کردیم، اما در نحوه حل طرح بندی کمی محدود بودیم، زیرا نیاز داشتیم که محتوا همیشه قابل مشاهده باشد و همچنان به تعدادی از کنترل ها و دکمه ها دسترسی سریع داشته باشیم. برای سایت‌های محتوای خالص مانند سایت‌های خبری، چیدمان روان بسیار منطقی است، اما برای برنامه‌های بازی مانند ما این یک مشکل بود. پیدا کردن طرح‌بندی که هم در جهت افقی و هم در جهت عمودی کار می‌کند، در حالی که همچنان نمای کلی خوبی از محتوا و روشی راحت برای تعامل داشته باشد، به یک چالش تبدیل شد. در پایان تصمیم گرفتیم Build را فقط در حالت افقی نگه داریم و به کاربر می‌گوییم دستگاه خود را بچرخاند.

حل کاوش در هر دو جهت بسیار ساده تر بود. ما فقط نیاز داشتیم که سطح بزرگنمایی سه بعدی را بسته به جهت گیری تنظیم کنیم تا تجربه ای ثابت داشته باشیم.

بیشتر طرح‌بندی محتوا توسط CSS کنترل می‌شود، اما برخی موارد مرتبط با جهت‌گیری باید در جاوا اسکریپت پیاده‌سازی شوند. ما متوجه شدیم که هیچ راه حل مناسبی برای استفاده از window.orientation برای شناسایی جهت گیری وجود ندارد، بنابراین در پایان فقط window.innerWidth و window.innerHeight را برای شناسایی جهت دستگاه مقایسه کردیم.

if( window.innerWidth > window.innerHeight ){
  //landscape
} else {
  //portrait
}

افزودن پشتیبانی لمسی

افزودن پشتیبانی لمسی به محتوای وب بسیار ساده است. تعامل اولیه، مانند رویداد کلیک، روی دسک‌تاپ و دستگاه‌های دارای قابلیت لمس یکسان عمل می‌کند، اما وقتی نوبت به تعاملات پیشرفته‌تر می‌رسد، باید رویدادهای لمسی را نیز مدیریت کنید: شروع لمس، حرکت لمسی و لمس. این مقاله اصول اولیه نحوه استفاده از این رویدادها را پوشش می دهد. اینترنت اکسپلورر از رویدادهای لمسی پشتیبانی نمی‌کند، اما در عوض از رویدادهای اشاره‌گر (pointerdown، pointermove، pointerup) استفاده می‌کند. رویدادهای اشاره گر برای استانداردسازی به W3C ارسال شده اند اما در حال حاضر فقط در اینترنت اکسپلورر پیاده سازی شده اند.

در حالت Explore 3D، ما همان پیمایشی را می‌خواستیم که پیاده‌سازی استاندارد Google Maps بود. با استفاده از یک انگشت برای حرکت در اطراف نقشه و دو انگشت برای بزرگنمایی. از آنجا که آثار به صورت سه بعدی هستند، حرکت چرخش دو انگشت را نیز اضافه کردیم. این معمولاً چیزی است که به استفاده از رویدادهای لمسی نیاز دارد.

یک تمرین خوب این است که از محاسبات سنگین مانند به روز رسانی یا رندر کردن 3D در کنترل کننده رویداد اجتناب کنید. در عوض، ورودی لمسی را در یک متغیر ذخیره کنید و روی ورودی در حلقه رندر requestAnimationFrame واکنش نشان دهید. این همچنین اجرای همزمان ماوس را آسان‌تر می‌کند، شما فقط مقادیر مربوطه ماوس را در همان متغیرها ذخیره می‌کنید.

با مقداردهی اولیه یک شی برای ذخیره ورودی شروع کنید و شنونده رویداد شروع لمسی را اضافه کنید. در هر کنترل کننده رویداد ما () event.preventDefault را فراخوانی می کنیم. این کار برای جلوگیری از ادامه پردازش رویداد لمسی توسط مرورگر است که می‌تواند باعث برخی رفتارهای غیرمنتظره مانند پیمایش یا مقیاس‌بندی کل صفحه شود.

var input = {dragStartX:0, dragStartY:0, dragX:0, dragY:0, dragDX:0, dragDY:0, dragging:false};
plateContainer.addEventListener('touchstart', onTouchStart);

function onTouchStart(event) {
  event.preventDefault();
  if( event.touches.length === 1){
    handleDragStart(event.touches[0].clientX , event.touches[0].clientY);
    //start listening to all needed touchevents to implement the dragging
    document.addEventListener('touchmove', onTouchMove);
    document.addEventListener('touchend', onTouchEnd);
    document.addEventListener('touchcancel', onTouchEnd);
  }
}

function onTouchMove(event) {
  event.preventDefault();
  if( event.touches.length === 1){
    handleDragging(event.touches[0].clientX, event.touches[0].clientY);
  }
}

function onTouchEnd(event) {
  event.preventDefault();
  if( event.touches.length === 0){
    handleDragStop();
    //remove all eventlisteners but touchstart to minimize number of eventlisteners
    document.removeEventListener('touchmove', onTouchMove);
    document.removeEventListener('touchend', onTouchEnd);
    //also listen to touchcancel event to avoid unexpected behavior when switching tabs and some other situations
    document.removeEventListener('touchcancel', onTouchEnd);
  }
}

ما ذخیره‌سازی واقعی ورودی را در کنترل‌کننده‌های رویداد انجام نمی‌دهیم، بلکه در کنترل‌کننده‌های جداگانه انجام می‌دهیم: handleDragStart، handleDragging و handleDragStop. این به این دلیل است که می‌خواهیم بتوانیم اینها را از کنترل‌کننده‌های رویداد ماوس نیز فراخوانی کنیم. به خاطر داشته باشید که اگرچه بعید است، کاربر ممکن است همزمان از لمس و ماوس استفاده کند. به جای رسیدگی مستقیم به آن پرونده، فقط مطمئن می شویم که هیچ چیز منفجر نمی شود.

function handleDragStart(x ,y ){
  input.dragging = true;
  input.dragStartX = input.dragX = x;
  input.dragStartY = input.dragY = y;
}

function handleDragging(x ,y ){
  if(input.dragging) {
    input.dragDX = x - input.dragX;
    input.dragDY = y - input.dragY;
    input.dragX = x;
    input.dragY = y;
  }
}

function handleDragStop(){
  if(input.dragging) {
    input.dragging = false;
    input.dragDX = 0;
    input.dragDY = 0;
  }
}

هنگام انجام انیمیشن‌های مبتنی بر حرکت لمسی، اغلب مفید است که حرکت دلتا را نیز از آخرین رویداد ذخیره کنید. به عنوان مثال، ما از این به عنوان پارامتری برای سرعت دوربین هنگام حرکت در تمام صفحات پایه در Explore استفاده کردیم، زیرا شما صفحات پایه را نمی کشید، بلکه در واقع در حال حرکت دوربین هستید.

function onAnimationFrame() {
  requestAnimationFrame( onAnimationFrame );

  //execute animation based on input.dragDX, input.dragDY, input.dragX or input.dragY
 /*
  /
  */

  //because touchmove is only fired when finger is actually moving we need to reset the delta values each frame
  input.dragDX=0;
  input.dragDY=0;
}

مثال جاسازی شده: کشیدن یک شی با استفاده از رویدادهای لمسی. پیاده سازی مشابه هنگام کشیدن نقشه کاوش سه بعدی در Build with Chrome: http://cdpn.io/qDxvo

حرکات چند لمسی

چندین چارچوب یا کتابخانه مانند Hammer یا QuoJS وجود دارد که می‌توانند مدیریت حرکات چند لمسی را ساده‌تر کنند، اما اگر می‌خواهید چندین حرکت را ترکیب کنید و کنترل کامل را به دست آورید، گاهی اوقات بهتر است این کار را از ابتدا انجام دهید.

برای مدیریت ژست‌های نیشگون گرفتن و چرخش، فاصله و زاویه بین دو انگشت را زمانی که انگشت دوم روی صفحه قرار می‌گیرد، ذخیره می‌کنیم:

//variables representing the actual scale/rotation of the object we are affecting
var currentScale = 1;
var currentRotation = 0;

function onTouchStart(event) {
  event.preventDefault();
  if( event.touches.length === 1){
    handleDragStart(event.touches[0].clientX , event.touches[0].clientY);
  }else if( event.touches.length === 2 ){
    handleGestureStart(event.touches[0].clientX, event.touches[0].clientY, event.touches[1].clientX, event.touches[1].clientY );
  }
}

function handleGestureStart(x1, y1, x2, y2){
  input.isGesture = true;
  //calculate distance and angle between fingers
  var dx = x2 - x1;
  var dy = y2 - y1;
  input.touchStartDistance=Math.sqrt(dx*dx+dy*dy);
  input.touchStartAngle=Math.atan2(dy,dx);
  //we also store the current scale and rotation of the actual object we are affecting. This is needed to support incremental rotation/scaling. We can't assume that an object is always the same scale when gesture starts.
  input.startScale=currentScale;
  input.startAngle=currentRotation;
}

در رویداد touchmove، سپس به طور مداوم فاصله و زاویه بین آن دو انگشت را اندازه گیری می کنیم. سپس از تفاوت بین فاصله شروع و فاصله فعلی برای تنظیم مقیاس استفاده می شود و از تفاوت بین زاویه شروع و زاویه فعلی برای تنظیم زاویه استفاده می شود.

function onTouchMove(event) {
  event.preventDefault();
  if( event.touches.length  === 1){
    handleDragging(event.touches[0].clientX, event.touches[0].clientY);
  }else if( event.touches.length === 2 ){
    handleGesture(event.touches[0].clientX, event.touches[0].clientY, event.touches[1].clientX, event.touches[1].clientY );
  }
}

function handleGesture(x1, y1, x2, y2){
  if(input.isGesture){
    //calculate distance and angle between fingers
    var dx = x2 - x1;
    var dy = y2 - y1;
    var touchDistance = Math.sqrt(dx*dx+dy*dy);
    var touchAngle = Math.atan2(dy,dx);
    //calculate the difference between current touch values and the start values
    var scalePixelChange = touchDistance - input.touchStartDistance;
    var angleChange = touchAngle - input.touchStartAngle;
    //calculate how much this should affect the actual object
    currentScale = input.startScale + scalePixelChange*0.01;
    currentRotation = input.startAngle+(angleChange*180/Math.PI);
    //upper and lower limit of scaling
    if(currentScale<0.5) currentScale = 0.5;
    if(currentScale>3) currentScale = 3;
  }
}

به طور بالقوه می توانید از تغییر فاصله بین هر رویداد حرکت لمسی به روشی مشابه با کشیدن استفاده کنید، اما این رویکرد اغلب زمانی مفیدتر است که می خواهید یک حرکت مداوم داشته باشید.

function onAnimationFrame() {
  requestAnimationFrame( onAnimationFrame );
  //execute transform based on currentScale and currentRotation
  /*
  /
  */

  //because touchmove is only fired when finger is actually moving we need to reset the delta values each frame
  input.dragDX=0;
  input.dragDY=0;
}

همچنین در صورت تمایل می‌توانید کشیدن شی را در حین انجام ژست‌ها و حرکات چرخشی فعال کنید. در این صورت، از نقطه مرکزی بین دو انگشت به عنوان ورودی برای کنترل کننده کشیدن استفاده می کنید.

مثال جاسازی شده: چرخش و مقیاس بندی یک شی به صورت دو بعدی. مشابه نحوه اجرای نقشه در Explore: http://cdpn.io/izloq

پشتیبانی از ماوس و لمس در همان سخت افزار

امروزه چندین رایانه لپ‌تاپ مانند Chromebook Pixel وجود دارد که از ورودی ماوس و لمسی پشتیبانی می‌کنند. اگر مراقب نباشید این ممکن است باعث برخی رفتارهای غیرمنتظره شود.

یک چیز مهم این است که شما نباید فقط پشتیبانی لمسی را تشخیص دهید و سپس ورودی ماوس را نادیده بگیرید، بلکه باید همزمان از هر دو پشتیبانی کنید.

اگر از event.preventDefault() در کنترل‌کننده‌های رویداد لمسی خود استفاده نمی‌کنید، برخی از رویدادهای شبیه‌سازی‌شده ماوس نیز اجرا می‌شوند تا بیشتر سایت‌های بهینه‌سازی شده غیرلمسی همچنان کار کنند. به عنوان مثال، برای یک بار ضربه زدن روی صفحه، این رویدادها ممکن است در یک توالی سریع و به ترتیب زیر اجرا شوند:

  1. شروع لمسی
  2. حرکت لمسی
  3. لمسی
  4. ماوس بر
  5. حرکت ماوس
  6. ماوس پایین
  7. موس
  8. کلیک

اگر تعاملات کمی پیچیده تری دارید، این رویدادهای ماوس ممکن است باعث ایجاد برخی رفتارهای غیرمنتظره شوند و اجرای شما را به هم بریزند. اغلب بهتر است از event.preventDefault() در کنترل‌کننده‌های رویداد لمسی استفاده کنید و ورودی ماوس را در کنترل‌کننده‌های رویداد جداگانه مدیریت کنید. باید بدانید که استفاده از event.preventDefault() در کنترل‌کننده‌های رویداد لمسی، از برخی رفتارهای پیش‌فرض مانند اسکرول و رویداد کلیک نیز جلوگیری می‌کند.

"در Build with Chrome ما نمی‌خواستیم زمانی که شخصی روی سایت دوبار ضربه می‌زند بزرگ‌نمایی انجام شود، حتی اگر این امر در اکثر مرورگرها استاندارد است. بنابراین ما از متا تگ viewport استفاده می‌کنیم تا به مرورگر بگوییم وقتی کاربر دوبار زوم نکند. ضربه بزنید. این کار تأخیر کلیک 300 میلی‌ثانیه را نیز حذف می‌کند، که پاسخگویی سایت را بهبود می‌بخشد. (تاخیر کلیک برای تمایز بین یک ضربه و دو ضربه در زمانی که بزرگنمایی با دو ضربه فعال است، وجود دارد.)

<meta name="viewport" content="width=device-width,user-scalable=no">

به یاد داشته باشید که هنگام استفاده از این ویژگی، این به شما بستگی دارد که سایت را در همه اندازه‌های صفحه قابل خواندن کنید، زیرا کاربر نمی‌تواند نزدیک‌تر زوم کند.

ورودی ماوس، لمسی و صفحه کلید

در حالت کاوش سه‌بعدی، می‌خواستیم سه راه برای پیمایش نقشه وجود داشته باشد: ماوس (کشیدن)، لمس (کشیدن، کوچک‌نمایی برای زوم و چرخش) و صفحه‌کلید (پیمایش با کلیدهای جهت‌نما). همه این روش‌های ناوبری کمی متفاوت عمل می‌کنند، اما ما از یک رویکرد در همه آنها استفاده کردیم. متغیرها را در کنترل کننده رویداد تنظیم کنید و در حلقه requestAnimationFrame بر اساس آن عمل کنید. حلقه requestAnimationFrame لازم نیست بداند از کدام روش برای پیمایش استفاده می شود.

به عنوان مثال، می‌توانیم حرکت نقشه (dragDX و dragDY) را با هر سه روش ورودی تنظیم کنیم. در اینجا پیاده سازی صفحه کلید است:

document.addEventListener('keydown', onKeyDown );
document.addEventListener('keyup', onKeyUp );

function onKeyDown( event ) {
  input.keyCodes[ "k" + event.keyCode ] = true;
  input.shiftKey = event.shiftKey;
}

function onKeyUp( event ) {
  input.keyCodes[ "k" + event.keyCode ] = false;
  input.shiftKey = event.shiftKey;
}

//this needs to be called every frame before animation is executed
function handleKeyInput(){
  if(input.keyCodes.k37){
    input.dragDX = -5; //37 arrow left
  } else if(input.keyCodes.k39){
    input.dragDX = 5; //39 arrow right
  }
  if(input.keyCodes.k38){
    input.dragDY = -5; //38 arrow up
  } else if(input.keyCodes.k40){
    input.dragDY = 5; //40 arrow down
  }
}

function onAnimationFrame() {
  requestAnimationFrame( onAnimationFrame );
  //because keydown events are not fired every frame we need to process the keyboard state first
  handleKeyInput();
  //implement animations based on what is stored in input
   /*
  /
  */

  //because touchmove is only fired when finger is actually moving we need to reset the delta values each frame
  input.dragDX = 0;
  input.dragDY = 0;
}

مثال جاسازی شده: استفاده از ماوس، لمس و صفحه کلید برای پیمایش: http://cdpn.io/catlf

خلاصه

تطبیق Build با Chrome برای پشتیبانی از دستگاه‌های لمسی با اندازه‌های مختلف صفحه‌نمایش تجربه‌ای یادگیری بوده است. تیم تجربه چندانی در انجام این سطح از تعامل بر روی دستگاه های لمسی نداشت و ما در این راه چیزهای زیادی یاد گرفتیم.

بزرگترین چالش این بود که چگونه می توان تجربه و طراحی کاربر را حل کرد. چالش های فنی مدیریت بسیاری از اندازه های صفحه نمایش، رویدادهای لمسی و مشکلات عملکرد بود.

حتی با وجود برخی چالش‌ها با سایه‌زن‌های WebGL در دستگاه‌های لمسی، این چیزی است که تقریباً بهتر از حد انتظار عمل می‌کند. دستگاه ها روز به روز قدرتمندتر می شوند و پیاده سازی WebGL به سرعت در حال بهبود است. ما احساس می کنیم که در آینده نزدیک بیشتر از WebGL در دستگاه ها استفاده خواهیم کرد.

حالا، اگر قبلاً این کار را نکرده‌اید، بروید و چیزی عالی بسازید !