دوباره باهم برای اولینبار
مقدمه
نزدیک به سی سال است که تجربههای رایانش رومیزی حول صفحهکلید و موشواره یا پد لمسی بهعنوان دستگاههای ورودی اصلی کاربر میچرخد. بااینحال، در دهه گذشته، تلفنهای هوشمند و رایانههای لوحی الگوی تعامل جدیدی را بهوجود آوردهاند: لمس. با معرفی دستگاههای Windows 8 با قابلیت لمسی، و اکنون با انتشار Chromebook Pixel فوقالعاده با قابلیت لمسی، لمس اکنون درحال تبدیل شدن به بخشی از تجربه مورد انتظار رایانه است. یکی از بزرگترین چالشها ساختن تجربههایی است که نهتنها در دستگاههای لمسی و دستگاههای دارای موشواره کار کند، بلکه در دستگاههایی که کاربر از هر دو روش ورودی استفاده میکند (گاهی بهطور همزمان) نیز کار کند!
این مقاله به شما کمک میکند تا درک کنید که چگونه قابلیتهای لمسی در مرورگر ساخته میشوند، چگونه میتوانید این مکانیزم رابط جدید را در برنامههای موجود خود ادغام کنید و چگونه لمس میتواند به خوبی با ورودی موشواره کار کند.
وضعیت لمس در پلاتفرم وب
iPhone اولین پلاتفرم محبوبی بود که میاناهای برنامهسازی کاربردی لمسی اختصاصی در مرورگر وب آن تعبیه شده بود. چندین فروشنده مرورگر دیگر میاناهای برنامهسازی کاربردی مشابهی ایجاد کردهاند که برای سازگاری با پیادهسازی iOS ساخته شدهاند، که اکنون با مشخصات «رویدادهای لمسی نسخه ۱» توصیف میشود. رویدادهای لمسی توسط Chrome و Firefox در رایانه، و توسط Safari در iOS و Chrome و مرورگر Android در Android، و همچنین سایر مرورگرهای تلفن همراه مانند مرورگر Blackberry پشتیبانی میشوند.
همکارم بوریس اسموس آموزش HTML5Rocks عالی درباره رویدادهای لمسی نوشته است که اگر قبلاً به رویدادهای لمسی نگاه نکردهاید، هنوز هم راه خوبی برای شروع است. درواقع، اگر قبلاً با رویدادهای لمسی کار نکردهاید، قبلاز ادامه دادن، آن مقاله را بخوانید. ادامه بده، منتظر میمانم.
همه انجام شد؟ اکنون که با رویدادهای لمسی آشنایی اولیه دارید، چالش نوشتن تعاملهای لمسی این است که تعاملهای لمسی میتوانند کاملاً متفاوت از رویدادهای موشواره (و پد لمسی و گوی پیمایش شبیهساز موشواره) باشند - و اگرچه میاناهای لمسی معمولاً سعی میکنند موشواره را شبیهسازی کنند، این شبیهسازی کامل یا بینقص نیست؛ واقعاً باید با هر دو سبک تعامل کار کنید و ممکن است مجبور شوید از هر میانایی بهطور مستقل پشتیبانی کنید.
مهمتر از همه: کاربر ممکن است هم از لمس و هم از موشواره استفاده کند
بسیاری از توسعهدهندگان سایتهایی ساختهاند که بهطور ایستا تشخیص میدهند آیا محیط از رویدادهای لمسی پشتیبانی میکند یا نه، و سپس فرض میکنند که فقط باید از رویدادهای لمسی (و نه موشواره) پشتیبانی کنند. این فرض اکنون اشتباه است - درعوض، فقط بهدلیل وجود رویدادهای لمسی به این معنی نیست که کاربر در درجه اول از آن دستگاه ورودی لمسی استفاده میکند. دستگاههایی مانند Chromebook Pixel و برخی از رایانههای کیفی Windows 8 اکنون از هر دو روش ورودی موشواره و لمسی پشتیبانی میکنند و در آینده نزدیک دستگاههای بیشتری از این روشها پشتیبانی خواهند کرد. در این دستگاهها، کاملاً طبیعی است که کاربران هم از موشواره و هم از صفحه لمسی برای تعامل با برنامهها استفاده کنند، بنابراین «پشتیبانی از لمس» با «عدم نیاز به پشتیبانی از موشواره» یکسان نیست. نمیتوانید اینطور فکر کنید که «باید دو سبک تعامل متفاوت بنویسم و بین آنها جابهجا شوم»، باید به این فکر کنید که چگونه هر دو تعامل هم باهم و هم بهطور مستقل کار خواهند کرد. در Chromebook Pixel خودم، اغلب از پد لمسی استفاده میکنم، اما دستم را بالا میبرم و صفحه را هم لمس میکنم - در همان برنامه یا صفحه، هر کاری را که در آن لحظه طبیعیتر بهنظر برسد انجام میدهم. ازسوی دیگر، برخیاز کاربران رایانه کیفی با نمایشگر لمسی بهندرت از نمایشگر لمسی استفاده میکنند - بنابراین وجود ورودی لمسی نباید کنترل موشواره را غیرفعال کند یا مانع آن شود.
متأسفانه، تشخیص اینکه آیا محیط مرورگر کاربر از ورودی لمسی پشتیبانی میکند یا نه میتواند دشوار باشد؛ در حالت ایدهآل، مرورگر در رایانه رومیزی همیشه باید پشتیبانی از رویدادهای لمسی را نشان دهد تا نمایشگر لمسی بتواند در هرزمانی متصل شود (مثلاً اگر نمایشگر لمسی متصلشده ازطریق KVM دردسترس قرار گیرد). به همه این دلایل، برنامههای شما نباید بین لمس و موشواره جابهجا شوند - فقط از هر دو پشتیبانی کنند!
پشتیبانی از موشواره و لمس بهطور همزمان
#۱ - کلیک کردن و تکضرب زدن - ترتیب «طبیعی» چیزها
مشکل اول این است که میاناهای لمسی معمولاً سعی میکنند کلیکهای موشواره را شبیهسازی کنند - بدیهی است، زیرا میاناهای لمسی باید روی برنامههایی کار کنند که قبلاً فقط با رویدادهای موشواره تعامل داشتهاند! میتوانید از این بهعنوان میانبر استفاده کنید - زیرا رویدادهای «کلیک» همچنان اجرا خواهند شد، چه کاربر با موشواره کلیک کرده باشد یا با انگشت روی صفحه ضربه زده باشد. اما این میانبر چند مشکل دارد.
ابتدا، هنگام طراحی تعاملات لمسی پیشرفتهتر باید مراقب باشید: وقتی کاربر از موشواره استفاده میکند، ازطریق رویداد کلیک پاسخ میدهد، اما وقتی کاربر صفحهنمایش را لمس میکند، هم رویداد لمس و هم رویداد کلیک رخ میدهد. برای یک کلیک، ترتیب رویدادها به این صورت است:
- touchstart
- touchmove
- touchend
- حرکت ماوس
- mousemove
- mousedown
- mouseup
- کلیک
البته این یعنی اگر رویدادهای لمسی مانند touchstart را پردازش میکنید، باید مطمئن شوید که رویدادهای mousedown و/یا click مربوطه را نیز پردازش نمیکنید. اگر بتوانید رویدادهای لمسی را لغو کنید (preventDefault() را در کنترلکننده رویداد فراخوانی کنید)، هیچ رویداد موشوارهای برای لمس ایجاد نخواهد شد. یکی از مهمترین قوانین کنترلکنندههای لمس این است:
بااینحال، این کار از سایر رفتارهای پیشفرض مرورگر (مانند پیمایش) نیز جلوگیری میکند - اگرچه معمولاً رویداد لمسی را بهطور کامل در گرداننده خود مدیریت میکنید و میخواهید کنشهای پیشفرض را غیرفعال کنید. بهطور کلی، یا باید همه رویدادهای لمسی را مدیریت و لغو کنید، یا از داشتن گرداننده برای آن رویداد اجتناب کنید.
دوم اینکه وقتی کاربر روی عنصری در صفحه وب در دستگاه همراه تکضرب میزند، صفحههایی که برای تعامل در تلفن همراه طراحی نشدهاند، بین رویداد touchstart و پردازش رویدادهای موشواره (mousedown) حداقل ۳۰۰ میلیثانیه تأخیر دارند. این کار را میتوان بااستفاده از Chrome انجام داد، میتوانید «شبیهسازی رویدادهای لمسی» را در «ابزارهای توسعهدهنده Chrome» روشن کنید تا به شما کمک کند میاناهای لمسی را در سیستم غیرلمسی آزمایش کنید!
این تأخیر برای این است که مرورگر زمان داشته باشد تا تشخیص دهد آیا کاربر اشاره دیگری انجام میدهد یا نه - بهویژه، بزرگنمایی با دوضربه. بدیهی است که این موضوع میتواند در مواردی که میخواهید پاسخ فوری به لمس انگشت داشته باشید مشکلساز شود. اقداماتی درحال انجام است تا سناریوهایی که این تأخیر بهطور خودکار در آنها رخ میدهد محدود شود.
اولین و آسانترین راه برای جلوگیری از این تأخیر این است که به مرورگر تلفن همراه «بگویید» که صفحه شما نیازی به بزرگنمایی ندارد - این کار را میتوان بااستفاده از یک ناحیه نمایش ثابت انجام داد، مثلاً با درج این کد در صفحه:
<meta name="viewport" content="width=device-width,user-scalable=no">
البته این همیشه مناسب نیست - این کار بزرگنمایی با نزدیک کردن انگشتان را غیرفعال میکند، که ممکن است به دلایل دسترسپذیری لازم باشد، بنابراین اگر اصلاً از آن استفاده میکنید، بهندرت استفاده کنید (اگر مقیاسبندی کاربر را غیرفعال میکنید، ممکن است بخواهید راه دیگری برای افزایش خوانایی نوشتار در برنامهتان ارائه دهید). همچنین، برای Chrome در دستگاههای کلاس رایانه که از لمس پشتیبانی میکنند، و سایر مرورگرها در پلاتفرمهای تلفن همراه وقتی صفحه دارای درگاههای نمای غیرقابلمقیاسبندی است، این تأخیر اعمال نمیشود.
۲: رویدادهای Mousemove با لمس کردن فعال نمیشوند
در این مرحله، توجه به این نکته مهم است که شبیهسازی رویدادهای موشواره در میانای لمسی معمولاً به شبیهسازی رویدادهای mousemove گسترش نمییابد - بنابراین اگر کنترل زیبا و موشوارهمحوری بسازید که از رویدادهای mousemove استفاده میکند، احتمالاً با دستگاه لمسی کار نخواهد کرد، مگراینکه بهطور خاص گردانندههای touchmove را نیز اضافه کنید.
مرورگرها معمولاً بهطور خودکار تعامل مناسب را برای تعاملهای لمسی در کنترلهای HTML پیادهسازی میکنند - بنابراین، برای مثال، کنترلهای HTML5 Range فقط زمانی کار میکنند که از تعاملهای لمسی استفاده کنید. بااینحال، اگر کنترلهای خودتان را پیادهسازی کردهاید، احتمالاً در تعاملات نوع کلیک و کشیدن کار نخواهند کرد؛ درواقع، برخیاز کتابخانههای رایج (مانند jQueryUI) هنوز بهطور بومی از تعاملات لمسی به این روش پشتیبانی نمیکنند (اگرچه برای jQueryUI، چندین اصلاحیه وصله میمون برای این مشکل وجود دارد). این یکی از اولین مشکلاتی بود که هنگام ارتقای برنامه Web Audio Playground برای کار با لمس با آن مواجه شدم - لغزندهها مبتنی بر jQueryUI بودند، بنابراین با تعاملات کلیک و کشیدن کار نمیکردند. به کنترلهای HTML5 Range تغییر دادم و کار کردند. البته، بهعنوان جایگزین، میتوانستم بهسادگی کنترلکنندههای touchmove را برای بهروزرسانی لغزندهها اضافه کنم، اما یک مشکل وجود دارد…
شماره ۳: Touchmove و MouseMove یکسان نیستند
یکی از تلههایی که دیدهام چند توسعهدهنده در آن افتادهاند، داشتن کنترلکنندههای touchmove و mousemove است که به همان مسیرهای کد فراخوانی میکنند. رفتار این رویدادها بسیار نزدیک است، اما تفاوتهای ظریفی دارد - بهویژه، رویدادهای لمسی همیشه عنصری را هدف قرار میدهند که لمس در آن شروع شده است، درحالیکه رویدادهای موشواره عنصری را هدف قرار میدهند که درحالحاضر زیر نشانگر موشواره قرار دارد. به همین دلیل است که رویدادهای mouseover و mouseout را داریم، اما رویدادهای touchover و touchout متناظر نداریم - فقط touchend داریم.
رایجترین روشی که این مشکل میتواند برای شما دردسرساز شود این است که بهطور اتفاقی عنصری را که کاربر شروع به لمس آن کرده است بردارید (یا جابهجا کنید). برای مثال، گردونه تصویری را تصور کنید که یک کنترلکننده لمسی روی کل گردونه دارد تا از رفتار پیمایش سفارشی پشتیبانی کند. با تغییر تصاویر دردسترس، برخیاز عناصر <img> را برمیدارید و عناصر دیگری را اضافه میکنید. اگر کاربر بهطور اتفاقی یکی از آن تصاویر را لمس کند و سپس شما آن را بردارید، گرداننده شما (که در عنصر ancestor img قرار دارد) دیگر رویدادهای لمسی را دریافت نخواهد کرد (زیرا این رویدادها به هدفی ارسال میشوند که دیگر در درخت وجود ندارد) - بهنظر میرسد که کاربر انگشت خود را در یک مکان نگه داشته است، حتی اگر آن را حرکت داده و درنهایت برداشته باشد.
البته میتوانید با اجتناب از برداشتن عناصری که دارای گرداننده لمسی هستند (یا اجداد آنها دارای گرداننده لمسی هستند) درحین فعال بودن لمس، از این مشکل جلوگیری کنید. بهعنوان جایگزین، بهترین راهنمایی این است که بهجای ثبت کنترلکنندههای touchend/touchmove ایستا، منتظر بمانید تا رویداد touchstart را دریافت کنید و سپس کنترلکنندههای touchmove/touchend/touchcancel را به هدف رویداد touchstart اضافه کنید (و آنها را در پایان/لغو بردارید). بهاینترتیب، حتی اگر عنصر هدف جابهجا/برداشته شود، همچنان رویدادهای مربوط به لمس را دریافت خواهید کرد. میتوانید کمی با این مورد اینجا بازی کنید - چارگوش قرمز را لمس کنید و درحالیکه آن را نگه داشتهاید، کلید escape را بزنید تا آن را از DOM بردارید.
شماره ۴: لمس کردن و :hover
استعاره اشارهگر موشواره موقعیت مکاننما را از انتخاب فعال جدا کرد و این امر به توسعهدهندگان اجازه داد تا از وضعیتهای نگه داشتن نشانگر برای پنهان کردن و نمایش اطلاعاتی که ممکن است برای کاربران مرتبط باشد استفاده کنند. بااینحال، درحالحاضر اکثر میاناهای لمسی «نگه داشتن» انگشت روی هدف را تشخیص نمیدهند - بنابراین ارائه اطلاعات مهم ازنظر معنایی (مثلاً ارائه بالاپر «این کنترل چیست؟») براساس نگه داشتن انگشت روی هدف توصیه نمیشود، مگراینکه روشی لمسپسند نیز برای دسترسی به این اطلاعات ارائه دهید. باید درباره نحوه استفاده از نگه داشتن نشانگر برای انتقال اطلاعات به کاربران دقت کنید.
جالب اینجاست که کلاس کاذب CSS :hover در برخی موارد میتواند توسط میاناهای لمسی فعال شود - ضربه زدن روی یک عنصر باعث میشود که درحالیکه انگشت پایین است، :active شود و همچنین حالت :hover را بهدست آورد. (در Internet Explorer، :hover فقط زمانی فعال است که انگشت کاربر روی صفحه باشد - مرورگرهای دیگر :hover را تا تکضرب بعدی یا حرکت موشواره فعال نگه میدارند.) این رویکرد خوبی برای کار کردن منوهای بازشو در میاناهای لمسی است - عارضه جانبی فعال کردن یک عنصر این است که حالت :hover نیز اعمال میشود. برای مثال:
<style>
img ~ .content {
display:none;
}
img:hover ~ .content {
display:block;
}
</style>
<img src="/awesome.png">
<div class="content">This is an awesome picture of me</div>
بهمحض اینکه روی عنصر دیگری تکضرب زده شود، عنصر دیگر فعال نیست و وضعیت نگه داشتن نشانگر ناپدید میشود، درست مثل اینکه کاربر از اشارهگر موشواره استفاده میکرد و آن را از روی عنصر برمیداشت. شاید بخواهید محتوا را در عنصر <a> قرار دهید تا آن را به ایستگاه پرش تبدیل کنید - به این ترتیب کاربر میتواند اطلاعات اضافی را با نگه داشتن نشانگر موشواره یا کلیک کردن، تکضرب زدن، یا فشار دادن کلید، بدون نیاز به جاوا اسکریپت، روشن یا خاموش کند. وقتی کارم را برای ساختن Web Audio Playground شروع کردم، از اینکه دیدم منوهای بازشوی من ازقبل بهخوبی با لمس کار میکنند بسیار خوشحال شدم، چون از این نوع ساختار استفاده کرده بودم!
روش بالا برای میاناهای مبتنی بر اشارهگر موشواره و همچنین میاناهای لمسی بهخوبی کار میکند. این برخلاف استفاده از مشخصههای «عنوان» در حالت نگه داشتن نشانگر است که هنگام فعال شدن عنصر نشان داده نخواهد شد:
<img src="/awesome.png" title="this doesn't show up in touch">
شماره ۵: دقت لمس درمقابل موشواره
درحالیکه موشوارهها ازنظر مفهومی از واقعیت جدا هستند، مشخص شده است که بسیار دقیق هستند، زیرا سیستمعامل اصلی معمولاً دقت پیکسل دقیق را برای مکاننما ردیابی میکند. ازسوی دیگر، توسعهدهندگان تلفن همراه متوجه شدهاند که لمس انگشت روی صفحهنمایش لمسی چندان دقیق نیست، عمدتاً بهدلیل اندازه سطح انگشت هنگام تماس با صفحهنمایش (و تا حدودی بهدلیل اینکه انگشتان شما مانع دیدن صفحهنمایش میشوند).
بسیاری از افراد و شرکتها پژوهشهای گستردهای درباره نحوه طراحی برنامهها و سایتهایی انجام دادهاند که برای تعامل مبتنی بر انگشت مناسب باشند، و کتابهای زیادی درباره این موضوع نوشته شده است. توصیه اصلی این است که اندازه هدفهای لمسی را با افزایش لایه افزایش دهید و احتمال ضربههای نادرست را با افزایش حاشیه بین عناصر کاهش دهید. (حاشیهها در مدیریت تشخیص ضربه برای رویدادهای لمس و کلیک گنجانده نمیشوند، اما لایهگذاری گنجانده میشود.) یکی از اصلاحات اصلی که باید در Web Audio Playground انجام میدادم، افزایش اندازه نقاط اتصال بود تا لمس دقیق آنها آسانتر شود.
بسیاری از فروشندگان مرورگر که با واسطهای مبتنی بر لمس سروکار دارند، منطقی را نیز در مرورگر معرفی کردهاند تا به هدفیابی عنصر صحیح هنگام لمس صفحه توسط کاربر کمک کند و احتمال کلیکهای نادرست را کاهش دهد - اگرچه این معمولاً فقط رویدادهای کلیک را تصحیح میکند، نه حرکتها را (اگرچه بهنظر میرسد Internet Explorer رویدادهای mousedown/mousemove/mouseup را نیز اصلاح میکند).
شماره ۶: گردانندههای لمس را محدود نگه دارید، وگرنه پیمایش را دچار پرش میکنند
همچنین مهم است که گردانندههای لمسی را فقط به عناصری که به آنها نیاز دارید محدود کنید؛ عناصر لمسی میتوانند پهنای باند بسیار بالایی داشته باشند، بنابراین مهم است که از گردانندههای لمسی در عناصر پیمایشی اجتناب کنید (زیرا پردازش شما ممکن است با بهینهسازیهای مرورگر برای پیمایش لمسی سریع و بدون لرزش تداخل داشته باشد - مرورگرهای مدرن سعی میکنند روی یک رشته GPU پیمایش کنند، اما اگر ابتدا مجبور باشند با جاوا اسکریپت بررسی کنند تا ببینند آیا هر رویداد لمسی توسط برنامه مدیریت میشود یا خیر، این کار غیرممکن است). میتوانید نمونهای از این رفتار را بررسی کنید.
یکی از راهنماییهایی که برای جلوگیری از این مشکل باید دنبال کنید این است که مطمئن شوید اگر فقط رویدادهای لمسی را در بخش کوچکی از واسط کاربری خود مدیریت میکنید، فقط گردانندههای لمسی را در آنجا متصل کنید (نه، مثلاً، در <body> صفحه)؛ بهطور خلاصه، دامنه گردانندههای لمسی خود را تا حد امکان محدود کنید.
شماره ۷: چندلمسی
آخرین چالش جالب این است که اگرچه ما از آن بهعنوان میانای کاربر «لمسی» یاد میکنیم، اما تقریباً در همه جا پشتیبانی درواقع برای چندلمسی است - یعنی میاناهای برنامهسازی کاربردی بیش از یک ورودی لمسی را در یک زمان ارائه میدهند. وقتی شروع به پشتیبانی از لمس در برنامههایتان میکنید، باید درنظر بگیرید که لمسهای چندگانه چگونه ممکن است بر برنامهتان تأثیر بگذارد.
اگر برنامههایی ساختهاید که عمدتاً با موشواره هدایت میشوند، پس به ساختن با حداکثر یک نقطه مکاننما عادت دارید - سیستمها معمولاً از چند مکاننمای موشواره پشتیبانی نمیکنند. برای بسیاری از برنامهها، شما فقط رویدادهای لمسی را به یک میانای مکاننما نگاشت میکنید، اما اکثر سختافزارهایی که برای ورودی لمسی رایانه دیدهایم میتوانند حداقل ۲ ورودی همزمان را مدیریت کنند و بهنظر میرسد اکثر سختافزارهای جدید حداقل ۵ ورودی همزمان را پشتیبانی میکنند. برای توسعه صفحهکلید پیانو روی صفحه، البته، میخواهید بتوانید از چندین ورودی لمسی همزمان پشتیبانی کنید.
W3C Touch API که درحالحاضر پیادهسازی شده است، هیچ API برای تعیین تعداد نقاط لمسی که سختافزار پشتیبانی میکند ندارد، بنابراین باید بهترین تخمین را برای تعداد نقاط لمسی که کاربران شما میخواهند استفاده کنید - یا، البته، به تعداد نقاط لمسی که در عمل میبینید توجه کنید و خود را با آن تطبیق دهید. برای مثال، در برنامه پیانو، اگر هرگز بیشاز دو نقطه لمس نمیبینید، شاید بخواهید چند واسط کاربر «آکورد» اضافه کنید. PointerEvents API دارای یک API برای تعیین قابلیتهای دستگاه است.
درحال رتوش کردن
امیدواریم این مقاله راهنماییهایی درباره چالشهای رایج در پیادهسازی تعاملهای لمسی در کنار تعاملهای موشواره به شما ارائه داده باشد. البته مهمتر از هر توصیه دیگری این است که باید برنامهتان را در محیطهای تلفن همراه، رایانه لوحی، و رایانه با ترکیب موشواره و لمس آزمایش کنید. اگر سختافزار لمسی+موشی ندارید، از «شبیهسازی رویدادهای لمسی» Chrome برای کمک به آزمایش سناریوهای مختلف استفاده کنید.
با پیروی از این راهنماییها، نهتنها امکانپذیر است، بلکه نسبتاً آسان است که تجربههای تعاملی جذابی بسازید که با ورودی لمسی، ورودی موشواره، و حتی هر دو سبک تعامل بهطور همزمان بهخوبی کار کنند.