Media queries זה מעולה, אבל…
Media queries הוא כלי מדהים, מתת אל שהוענקה למפתחי אתרים אשר מעוניינים לבצע שינויים קטנים בגליונות העיצוב שלהם, על-מנת לאפשר חוויית שימוש טובה יותר למשתמשים אשר גולשים לאתר ממכשירים בעלי גודל תצוגה מגוון. Media queries למעשה מאפשר לך לבצע התאמות ל-CSS של אתרך בהתאם לגודל מסך. לפני שאתה צולל פנימה לתוך מאמר זה, למד עוד בנושא של עיצוב גמיש (responsive design) וצפה בכמה דוגמאות טובות של שימוש ב-media queries כאן: mediaqueri.es.
כמו שבראד פרוסט (Brad Frost) ציין במאמרו המוקדם, שינוי המראה הוא רק אחד מהדברים בהם יש להתחשב כאשר בונים אתר לעולם התוכן הנייד. אם הדבר היחיד שתעשה כשתבנה לך אתר למכשיר נייד הוא להתאים את הנראות שלו באמצעות media queries, אזי נקבל את המצבים הבאים:
- כל המכשירים מקבלים את אותו ה-JavaScript, CSS ותכנים (תמונות, וידאו), מה שמוביל לזמן טעינה ארוך מהמבוקש.
- כל המכשירים מקבלים את אותו ה-DOM, דבר העשוי לגרום למפתחים לכתוב CSS בצורה מסובכת יתר על המידה.
- גמישות נמוכה בהתאמת פעולות מובנות המותאמות לכל מכשיר ומכשיר.
אפליקציות צריכות יותר מ-media queries.
אל תבינו אותי לא נכון. אני לא שונא עיצוב גמיש באמצעות media queries, ובהחלט מאמין כי יש לו מקום בעולם הזה. מעבר לכך, חלק מהנושאים המוזכרים לעיל ניתנים לפיתרון על-ידי גישות כמו תמונות גמישות, טעינת סקריפטים באופן דינאמי, וכו'. עם זאת, בנקודה מסויימת, אתה עלול למצוא את עצמך יוצר יותר מדי שינויים על גבי שינויים עד למצב בו אולי כבר עדיף לשחרר גרסאות שונות.
בעת בה ממשק המשתמש שאתה בונה הופך יותר ויותר מסובך, ואתה מתכנס לאפליקציות דף-יחיד (Single-page webapps), תרצה לעשות יותר על מנת להתאים את ממשק המשתמש לכל סוג של מכשיר. כתבה זו תדריך אותך איך לבצע התאמות אלו בהשקעה מינימלית. הגישה הכללית לנושא מדברת על קטלוג של מכשירי המבקרים באפליקציה לקטגוריה מתאימה, ויצירת גרסה מתאימה לקטגוריה זו, תוך מיקסום שימוש חוזר בקוד בין הגרסאות.
לאילו קטגוריות מכשירים אתה מכוון?
ישנה כמות גדולה של מכשירים מקושרי אינטרנט בשוק, וכמעט בכולם יש דפדפנים. הסיבוך טמון במגוון שלהם: macbook, PC, אייפון, אייפד, מכשירי אנדרואיד עם ממשק מגע, גלגלת, מקלדת, ממשק קול, מכשירים עם סנסורי לחץ, שעונים חכמים, טוסטרים ומקררים ועוד הרבה. חלקם מקיפים אותנו מכל עבר, בעוד אחרים נדירים למראה.
מגוון מכשירים (מקור).
על-מנת ליצור חווית משתמש איכותית, עלייך לדעת מי הוא קהל היעד שלך ובאילו מכשירים הוא משתמש. אם תבנה ממשק משתמש ללקוח שעובד בשולחן עבודה עם עכבר ומקלדת ותיתן אותו למשתמש סמארטפון, הממשק שלך יהיה מתסכל כיוון שהוא מעוצב לגודל מסך אחר ותצורת קלט שונה.
ישנן שתי קצוות לקשת הגישות:
- בניית גרסה אחת שעובדת על כל המכשירים. חווית המשתמש תפגע, שכן למכשירים שונים יש מגבלות עיצוב שונות.
- בניית גרסה לכל מכשיר בו אתה רוצה לתמוך. זה ייקח נצח, כיוון שתבנה יותר מדי גרסאות לאפליקציה. בנוסף, כשיצא לשוק הסמארטפון הבא (מה שקורה כרגע על בסיס שבועי בערך) תצטרך לבנות שוב עוד גרסה.
נוצר כאן עיקרון של מאזן אינטרסים: ככל שיהיו ברשותך יותר קטגוריות מכשירים, כן תגדל חווית המשתמש, אך מצד שני תידרש לעבוד יותר על עיצוב, יישום, ותחזוקה.
יצירת גרסה נפרדת לכל קטגוריית מכשירים עליה החלטת, עשויה להיות רעיון טוב בכל הנוגע לביצועים או אם הגרסאות השונות אותן אתה רוצה לספק לקטגוריות השונות נבדלות בצורה נרחבת מאחת לשניה. אחרת, עיצוב ווב גמיש זו גישה סבירה בהחלט.
פתרון אפשרי
להלן פשרה: חלק את המכשירים לקטגוריות, ועצב את החוויה האפשרית הטובה ביותר לכל קטגוריה. הקטגוריות בהן תבחר תלויות במוצר שלך וקהל היעד. הנה דוגמא לחלוקה שתפסה תאוצה בקרב מכשירים בעלי יכולת גלישה שקיימים כיום.
- מסך קטן+מגע (לרוב מכשירים ניידים)
- מסך רחב+מגע (לרוב טבלטים)
- מסך רחב+מקלדת/עכבר (לרוב מחשבים אישיים או ניידים)
זו אחת מיני אפשרויות רבות, אבל אפשרות שטומנת בחובה הרבה היגיון בזמן כתיבה. נעדרים מן הרשימה למעלה הם מכשירים קטנים ללא מסך מגע (לדוגמא מכשירים סלולרים מבוססי כפתורים, ו-ebooks מסויימים). למרות זאת, לרובם יש ניווט באמצעות מקלדת או תוכנה לקריאת מסך מותקנת, אשר יעבדו היטב אם תבנה את האתר שלך עם נגישות מתאימה.
דוגמאות לאפליקציות ווב עם גורמי תצורה מוגדרים
קיימות הרבה דוגמאות של תכני-אינטרנט המציגים גרסאות שונות לחלוטין לגורמי תצורה שונים. החיפוש של גוגל עושה את זה, כמ כן גם פייסבוק. השינויים הנדרשים לביצוע כוללים התאמות ביצועיות (תכנים מותאמים, רנדור של עמודים) ועוד חוויות משתמש כלליות.
בעולם האפליקציות הנייטיב, מפתחים רבים בוחרים להתאים אישית את חווית השימוש לקטגורית מכשיר מסויימת. למשל, ב-Flipboard לאייפדים יש ממשק משתמש שונה מאד בהשוואה למקביל אליו באייפון. גרסת הטבלט מותאמת לשימוש בשתי ידיים והטיית מסך אופקית, בעוד שגרסת הטלפון מכוונת לשימוש ביד אחת והטיית מסך אנכית. אפלקציות iOS רבות אחרות מציגות גרסאות נבדלות מאחת לשניה באופן ניכר, כגון Things(רשימת todo) ו-Showyou (וידאו חברתי) הנראה מטה:

ממשק משתמש שונה לחלוטין בין הטלפון לטאבלט
גישה #1: זיהוי צד-שרת
בשרת עצמו, יש לנו יכולת מועטת של הבנת המכשיר שאנו מטפלים בו. ככל הנראה, ההכוונה הטובה ביותר שנוכל לקבל היא דרך מחרוזת ה-User Agent, אשר מסופק לנו דרך ה-User Agent header בכל בקשה. לכן, אותה גישת UA sniffing תעבוד כאן. למעשה, פרוייקטים כמו Deviceatlasו- WURFL כבר עושים זאת (ומספקים עוד מידע רב נוסף על המכשיר).
לרוע המזל, כל שיטה מציגה אתגרים משלה. WURFL הוא מאד כבד, ומכיל 20 MB של XML, ועשוי לפגום משמעותית בתקורה בכל בקשה. יש פרוייקטים שמפצלים את ה-XML מסיבות ביצועיות. DeviceAtlas היא לא תוכנת קוד-פתוח ונדרש תשלום על מנת לקבל רישיון להשתמש בה.
ישנן אלטרנטיבות פשוטות-חינמיות יותר כמו פרוייקט Detect Mobile Browsers. החיסרון, כמובן, הוא שפרטי זיהוי המכשיר יהיו דלים יותר. נוסף לכך, הוא מבדיל רק בין מכשירים ניידים ללא ניידים, ומספק תמיכה מאד מוגבלת לטבלטים באמצעות ביצוע שינויים ספציפיים למטרה זו.
גישה #2: זיהוי צד-לקוח
אנו יכולים ללמוד רבות על דפדפן ומכשיר המשתמש על-ידי זיהוי מאפיינים (feature detection). המאפיינים העיקריים אותם אנו צריכים לבדוק הם אם המכשיר הוא בעל ממשק מגע והאם מדובר במסך גדול או קטן.
עלינו לצייר איפשהו גבול שיבדיל בין מכשיר מגע בעל מסך גדול ומכשיר מגע בעל מסך קטן. ומה בדבר מקרים חריגים כגון ה- Galaxy Noteחמישה אינץ'? התרשים הבא מתאר מספר מכשירים מבוססי אנדרואיד ו-iOS פופולאריים (עם רזולוציות התצוגות בהתאמה). הכוכבית מהווה אינדיקציה לאם המכשיר מגיע או יכול להגיע עם צפיפות כפולה. למרות שצפיפות הפיקסלים עשויה להיות כפולה, ה-CSS עדיין מדווח על אותם גדלים.
הארה קצרה על פיקסלים ב-CSS: פיקסלים של CSS בגלישה הניידת אינם זהים לפיקסלים של התצוגה. מכשירי הרטינה של iOS הציגו לראשונה את הפרקטיקה של הכפלת צפיפות הפיקסלים. (כדוגמת אייפון 3 לעומת אייפון 4, אייפד 2 לעומת אייפד 3). ה-Mobile Safari UA של הרטינה עדיין מדווח על אותו גודל המסך בכדי למנוע קריסה של האתר. בעוד שמכשירים אחרים (כמו אנדרואיד) מקבלים תצוגות ברזולוציה גבוהה יותר, הם משתמשים באותו טריק של רוחב מכשיר.

רזולוציית מכשירים (בפיקסלים).
דבר המסבך יותר את הנושא, הוא החשיבות שבהתייחסות לשני המצבים של המכשיר- מצבו האופקי ומצבו האנכי. אנחנו לא רוצים לטעון מחדש את הדף או לטעון קודים נוספים בכל פעם בו נטה את המכשיר לתצוגה שונה, אך אנו כן רוצים להתאים את העמוד בצורה שונה.
בדיאגרמה הבאה, הריבועים מייצגים את המימדים המקסימליים של כל מכשיר, כתוצאה מהשמת קווי המתאר של מצבו האופקי והאנכי של המכשיר יחד (והשלמת הריבוע):

רזולוצית מצב אופקי+מצב אנכי (בפיקסלים)
על-ידי הצבת רף של 650 פיקסל, אנחנו מקטלגים את האייפון וגלקסי נקסוס כמכשיר מגע קטן, ואייפד וגלקסי טאב כטבלטים. הגלקסי נואט המתנדנד בין שתי הקטגוריות יקבל במקרה זה את סיווגו כמכשיר סלולרי, ויקבל את הממשק שלו ככזה.
אם כך, אסטרטגיה מתקבלת על הדעת עשויה להיראות כך:
if (hasTouch) {
if (isSmall) {
device = PHONE;
} else {
device = TABLET;
}
} else {
device = DESKTOP;
}
ראה דוגמית קטנה של גישת זיהוי–מאפיין בפעולה.
גישה אלטרנטיבית למקרה זה היא להשתמש ב-UA sniffing על-מנת לגלות את סוג המכשיר. בגדול אתה יוצר אמצעי גילוי ובודק אותם מולnavigator.userAgent של המשתמש.
קוד דמה נראה כמו משהו כזה:
var ua = navigator.userAgent;
for (var re in RULES) {
if (ua.match(re)) {
device = RULES[re];
return;
}
}
ראה דוגמא של גישת זיהוי UA בפעולה.
הערה על טעינת צד-לקוח
אם אתה משתמש בגישת זיהוי UA בשרת שלך, אתה יכול להחליט באיזה CSS, JavaScript או DOM אתה משתמש כשאתה מקבל בקשה. לעומת זאת, אם אתה בוחר בגישת זיהוי צד-לקוח, המצב יותר מורכב. יש בפנייך מספר אפשרויות:
- 1. להפנות מחדש ל-URL של סוג מכשיר מסויים, המכיל את הגרסה המתאימה למכשיר המבוקש.
- 2. לטעון את התכנים למכשיר הספציפי באופן דינאמי.
הגישה הראשונה היא פשוטה, הפנייה כמו window.location.href = '/tablet'. אולם כעת ה-location יכיל את המידע על המכשיר, כך שאולי תבחר להשתמש ב- History API בכדי לנקות את ה-URL. בעיה בגישה זו היא שהיא כרוכה בהפניה מחדש אשר עלולה לארוך זמן רב, במיוחד במכשירים סלולרים.
הגישה השנייה היא מעט יותר מורכבת ליישום. אתה צריך איזשהו מנגון שיטעין באופן דינאמי את ה-CSS ו-JS. ובנוסף (תלוי-דפדפן) אולי לא תוכל לעשות דברים כגון התאמה אישית <meta viewport>. וכמו כן כיוון שאין הפנייה מחדש אתה נתקע עם כתובת ה-HTML המקורית שאליו הופנה המכשיר. כמובן שניתן להתגבר על-כך עם Javascript, אך זה עשוי להיות איטי או להצטייר בצורה שאינה אלגנטית, כתלות באפליקציה.
להחליט על לקוח או שרת
אלו הן היתרונות לכל גישה:
בעד לקוח:
- יותר עמיד לטווח הארוך שכן מבוסס על גודל מסך/יכולות ולא על UA
- אין צורך בעדכון מתמיד של רשימת ה-UA
בעד שרת:
- שליטה מלאה על איזו גרסה תותאם לאיזה מכשיר
- ביצועים טובים יותר: אין צורך בהפניית הלקוח מחדש או בטעינה דינאמית.
ההעדפה האישית שלי היא להתחיל עם device.js וזיהוי צד-לקוח. כאשר האפליקציה שלך תתפתח, אם תבחין בכך שההפניה מחדש של צד-לקוח גורעת באופן משמעותי מהביצועים, תוכל בקלות להסיר את קוד ה-device.js ולשתול במקומו זיהוי UA על השרת.
הכרת device.js
device.js הוא נקודת פתיחה על מנת לבצע זיהוי מכשיר סמנטי, מבוסס media query ללא צורך בהגדרות צד-שרת, ולחסוך זמן ומאמץ הנדרשים לניתוח מחרוזת ה-User Agent.
הרעיון הוא להגדיר אוסף תגיות ידודתיות למנועי החיפוש (link rel=alternate) בתוך התגית ה-<head> הנותנות אינדיקציה לאילו גרסאות של האתר אתה רוצה לספק בכל מצב.
<link rel="alternate" href="http://foo.com" id="desktop"
media="only screen and (touch-enabled: 0)">
לאחר מכן, תוכל לבצע זיהוי צד-שרת UA ולהתמודד עם ההפנייה מחדש בעצמך, או להשתמש ב-device.js על מנת לעשות הפניית צד-לקוח מבוססת איפיון.
למידע נוסף, ראה עמוד פרוייקט device.js וגם אפצליקציית דמה המשתמשת ב-device.js להפניית צד-לקוח.
המלצה: MVC עם שכבת תצוגה מסויימת ע"פ תצורה
כעת אתה בטח חושב שאני אומר לך לבנות שלוש אפליקציות שונות ונפרדות, אחת לכל קטגורית מכשיר. לא! שיתוף קוד (code sharing) הוא המפתח.
לתקוותי, אתה משתמש פריימוורק מבוסס MVC, כמו Backbone, Ember וכו'. במידה וכן, אתה מכיר את עיקרון ההפרדה של השכבות, כלומר ששכבת התצוגה שלך (view layer) צריכה להיות נפרדת מהלוגיקה שלך (model layer). אם זה חדש לך, תוכל להתחיל במידע על MVC ו-MVCב-JavaScript.
הכתיבה "מותאמת-מכשירים" תתאים בצורה נהדרת למסגרת ה-MVC. תוכל בקלות להעביר את ה-views לקבצים נפרדים, וליצור מראה מותאם אישית לכל קטגורית מכשירים. אחרי-כן תוכל ליישם את את אותו הקוד לכל המכשירים, מלבד שכבת ה-view.

MVC מותאם מכשירים שונים
לפרוייקט שלך עשוי להיות מבנה דומה (כמובן, אתה חופשי לבחור את המבנה המתאים ביותר בהתאם לאפליקציה שלך).
models/ (מודלים משותפים)
item.js
item-collection.js
controllers/ (קונטרולרים משותפים)
item-controller.js
versions/ (דברים המתאימים למכשירים ספציפיים)
tablet/
desktop/
phone/ (קוד למכשיר ספציפי)
style.css
index.html
views/
item.js
item-list.js
סוג המבנה מאפשר לך שליטה מלאה בתכנים אשר כל גרסה טוענת, כיוון שיש לך HTML, CSS ו-JavaScript מותאמים לכל מכשיר. טמון בכך הרבה כח, ואתה יכול להוביל את הפיתוח הקל והעוצמתי ביותר של אינטרנט מותאם מכשירים שונים, בלי להסתמך על טריקים כגון תמונות מותאמות (adaptive images).
ברגע שתריץ את כלי הבנייה המועדף עלייך, אתה תחבר ותצמצמם את כל ה-JavaScript ו-CSS לקבצים בודדים לקבלת טעינה מהירה יותר, עם תוצאה של HTML שנראה בערך כך (למכשיר סלולרי עם שימוש ב-device.js):
<!doctype html>
<head>
<title>Mobile Web Rocks! (Phone Edition)</title>
<!-- Every version of your webapp should include a list of all
versions. -->
<link rel="alternate" href="http://foo.com" id="desktop"
media="only screen and (touch-enabled: 0)">
<link rel="alternate" href="http://m.foo.com" id="phone"
media="only screen and (max-device-width: 650px)">
<link rel="alternate" href="http://tablet.foo.com" id="tablet"
media="only screen and (min-device-width: 650px)">
<!-- Viewport is very important, since it affects results of media
query matching. -->
<meta name="viewport" content="width=device-width">
<!-- Include device.js in each version for redirection. -->
<script src=”device.js”></script>
<link rel=”style” href=”phone.min.css”>
</head>
<body>
<script src=”phone.min.js”></script>
</body>
שים לב שה-medie query של ה- (touch-enabled: 0) אינו סטנדרטי (מיושם רק בפיירפוקס באמצעות התחילית moz) אך מטופל בצורה טובה (תודות לModernizr.touch) ע"י device.js.
דריסת גרסה
זיהוי מכשיר עשוי לעתים להיות שגוי, ובמקרים מסויימים, המשתמש יעדיף להסתכל על ממשק הטבלט בעודו משתמש בטלפון (אולי הוא משתמש בגלקסי נואוט), לכן חשוב לתת למשתמש אפשרות בחירה של הגרסה, אם ברצונו לבחור זאת באופן ידני.
האפשרות השכיחה היא לספק קישור לגרסת שולחן העבודה מהמכשיר הנייד. זה פשוט מספיק ליישום, אך device.js תומך בפונקציה זו עם פרמטר ה-device GET.
סיכום
לסיכום, כשבונים ממשק משתמש של דף יחיד (single-page) מותאם מכשירים שונים, שאינו משתלב בצורה מושלמת עם עולם העיצוב הגמיש, פעלו כך:
- בחר אוסף של קטגוריות מכשירים בהן תרצו לתמוך, ובחרו קריטריונים על-פיהם תחלקו את המכשירים לקטגוריות.
- בנו את אפליקציית ה-MVC עם הפרדה רחבה בין הנראות (views) לשאר קוד הבסיס.
- השתמשו ב-device.js על מנת לבצע זיהוי צד-לקוח לקטגוריות המכשירים.
- כשאתם מוכנים, רכזו את הקוד (script) וגליונות העיצוב לכל אחד מקטגוריות המכשירים.
- אם הביצועים מהפניית צד-לקוח נפגעים, זנחו את device.js ועבור לשיטת זיהוי צד-שרת.