בניית מחשבון בעזרת JavaScript, HTML ו- CSS

בניית מחשבון בעזרת JavaScript, HTML ו- CSS

  • פרסומת

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

חלק א׳ – HTML

החלק הראשון הוא יצירת השלד של המחשבון. בעזרת HTML ניתן להגדיר את מבנה המסמך אותו נעצב בהמשך בעזרת CSS.

המסמך

בראש המסמך ניתן לראות שני קישורים לפונטים חיצוניים. הראשון הוא פונט מקבוצת Goggle Fonts. השני הוא פונט המכיל סמלים בלבד, הנקרא Font Awesome. שני הפונטים הם חינמיים ומותרים לשימוש לכל מטרה. ניתן להוריד את הפונטים ולשמור אותם על השרת, אך עדיף להשתמש ב-CDN (Content Delivery Network) משום שבשיטה זו יש סיכוי גדול יותר שהפונטים כבר שמורים במחשב הלקוח.

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <title>Cool JavaScript Calculator</title>
        <link href='http://fonts.googleapis.com/css?family=Open+Sans:600' rel='stylesheet' type='text/css' />
        <link href="http://maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet" />
    </head>
    <body>
        <h1>JavaScript Calculator</h1>
        <div class="calculator"></div>
    </body>
</html>

הלחצנים

הלחצנים של המחשבון האלקטרוני
את הלחצנים של המחשבון ניתן ליצור בתור טבלה (עם עיצוב אגרסיבי) כשבכל שורה ישנם 4 לחצנים. באופן הזה ניתן בקלות ליצור לחצנים בצורות שונות בגובה ובאורך תוך שמירה על התבנית המוגדרת. לדוגמה, הלחצן של החיבור (+) הוא באורך של שתי שורות, וזה נעשה על ידי הוספה של האטריביוט rowspan="2".
בשביל להגדיר את הפעולה של כל לחצן, אפשר להוסיף תכונה (attribute) לכל אלמנט. במקרה הנ״ל ניתן לחלק את הלחצנים לשתי קבוצות: מספרים ופונקצית. לחצני מספרים יקבלו את התכונה data-token="token_name" ולחצנים שמבצעים פונקציה כמו חיבור או חיסור יקבלו את התכונה data-func="function_name". האטריביוטים האלו ישמשו בהמשך בשביל להעניק פונקציונאליות ללחצנים בעזרת JavaScript.

<table class="keys">
    <tbody>
        <tr>
            <td class="red key" data-func="clear">C</td>
            <td class="red key" data-func="cancel_entry">CE</td>
            <td class="gray key power" data-token="op_pow">x<span>y</span></td>
            <td class="gray key divide" data-token="op_divide">÷</td>
        </tr>
        <tr>
            <td class="blue key" data-token="num_7">7</td>
            <td class="blue key" data-token="num_8">8</td>
            <td class="blue key" data-token="num_9">9</td>
            <td class="gray key" data-token="op_multiply"><i class="fa fa-times"></i></td>
        </tr>
        <tr>
            <td class="blue key" data-token="num_4">4</td>
            <td class="blue key" data-token="num_5">5</td>
            <td class="blue key" data-token="num_6">6</td>
            <td class="gray key substract" data-token="op_substract">–</td>
        </tr>
        <tr>
            <td class="blue key" data-token="num_1">1</td>
            <td class="blue key" data-token="num_2">2</td>
            <td class="blue key" data-token="num_3">3</td>
            <td class="gray key add" data-token="op_add" rowspan="2">+</td>
        </tr>
        <tr>
            <td class="blue key" data-token="num_dot">.</td>
            <td class="blue key" data-token="num_0">0</td>
            <td class="blue key eval" data-func="eval">=</td>
        </tr>
    </tbody>
</table>

חלק ב׳ – CSS

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

פונטים

בשביל להעניק למסך של המחשבון מראה אותנטי, השתמשתי בפונט שנקרא BPdots. את הפונט הזה צירפתי בעזרת CSS משום שאין CDN שמאחסן אותו (למיטב ידיעתי).
מסך המחשבון עם פונט מותאם

@font-face {
    font-family: bpdots;
    src: url("BPdots.otf") format("opentype");
}

גוף המחשבון

בעזרת CSS3 ניתן לצבוע את גוף המחשבון ב-gradient, מה שנותן לו נפח מסויים. ניתן לשחק עם הצל בשביל לחזק את תחושת הנפח. משום ש-CSS3 היא גריסה יחסית חדשה של CSS, כדאי להוסיף prefix לכל דפדפן בשביל להבטיח תאימות דפדפנים בגירסאות ישנות יותר. לחצו כאן למידע נוסף אודות תאימות דפדפנים.

.calculator {
    width: 250px;
    background-color: rgb(44, 62, 80);
    background-image: linear-gradient( to right, rgb(51, 75, 99) 0%, rgb(75, 101, 128) 40%, rgb(75, 101, 128) 60%, rgb(51, 75, 99) 100%);
    margin: 10px auto;
    border-width: 1px;
    border-style: solid;
    border-top-color: rgba(255, 255, 255, 0.6);
    border-bottom-color: black;
    border-right-color: black;
    border-left-color: black;
    border-radius: 8px;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.4), inset 0 0 3px 4px rgba(0, 0, 0, 0.5);
    border-top: 4px solid rgba(255, 255, 255, 0.2);
}

האנימציה של הסמן

גם הסמן המהבהב נעשה בעזרת CSS בלבד. את הרעיון מצאתי ב- Codepen, ושיניתי את הקוד במקצת בשביל להתאימו למחשבון. הרעיון פשוט: האנימציה מתחילה ומסתיימת בצבע שקוף, ובאמצע היא מעניקה לטקסט צבע שחור. האנימציה חוזרת על עצמה ללא הפסקה. ההגדרה step-end אומרת לדפדפן לקפוץ ממצב אחד לשני ללא מצב ביניים. לחצו כאן למידע נוסף על timing functions.

#typed-cursor {
    font-size: 22px;
    line-height: 24px;
    vertical-align: top;
    font-weight: bold;
    color: black;
    
    -webkit-animation: 1s blink step-end infinite;
    -moz-animation: 1s blink step-end infinite;
    -ms-animation: 1s blink step-end infinite;
    -o-animation: 1s blink step-end infinite;
    animation: 1s blink step-end infinite;
}

@keyframes "blink" {
    from, to {
        color: transparent;
    }
    50% {
       color: black;
    }
}

עיצוב הלחצנים

כל הלחצנים יורשים חלק גדול מהמראה שלהם מהקלאס .key. גם כאן השתמשתי ב-gradient בשביל להעניק תחושה של עומק ללחצנים. כאשר הסמן מרחף מעל אחד הלחצנים, צבע ה-border והצל משתנים, כפי שמוגדר ב- .key:hover

.key {
    width:25%;
    cursor: pointer;
    font-family: 'Open Sans', sans-serif;
    color: rgb(236, 240, 241);
    font-size: 18px;
    text-align: center;
    padding: 8px 0;
    border-radius: 5px;
    border-width: 3px;
    border-style: solid;
    border-top-color: rgba(255, 255, 255, 0.2);
    border-left-color: rgba(0, 0, 0, 0.1);
    border-right-color: rgba(0, 0, 0, 0.1);
    border-bottom-color: rgba(0, 0, 0, 0.3);
    box-shadow: 0 0 4px 2px rgba(0, 0, 0, 0.7);
    transition: all 0.2s ease-out;
}
.key:hover {
    border: 3px solid rgba(226, 154, 26, 0.6);
    box-shadow: 0 0 4px 2px rgba(0, 0, 0, 0.7), inset 0 0 6px rgba(0, 0, 0, 0.4);
    color: rgba(226, 170, 26, 1);
    text-shadow: 0 0 10px rgba(255, 218, 153, 1);
}

לאחר מכן ניתן, בעזרת שורת קוד בודדת, לייצר שלושה סוגי לחצנים בצבעים אדום, כחול ואפור.

.red.key {background-image: linear-gradient( to bottom, rgb(150, 52, 52) 0%, rgb(190, 77, 77) 50%, rgb(150, 52, 52) 100%);}
.blue.key {background-image: linear-gradient( to bottom, rgb(44, 62, 80) 0%, rgb(58, 80, 102) 50%, rgb(44, 62, 80) 100%);}
.gray.key {background-image: linear-gradient( to bottom, rgb(110, 133, 151) 0%, rgb(130, 155, 175) 50%, rgb(110, 133, 151) 100%);}

חלק ג׳ – JavaScript

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

/**
 * Calculator class
 */
function Calculator() {}

/**
 * Screen class
 */
function Screen( calculator ) {}

/**
 * Keyboard class
 */
function Keyboard( calculator ) {}

/**
 * Cursor class
 */
function Cursor( screen ) {}

גוף המחשבון

גוף המחשבון מכיל את ״המוח״ של המחשבון בכך שהוא אחראי לתרגם את הקלט מהמקלדת למשפט מתמטי ולחשב אותו בהתאם. החלק הראשון הוא מיפוי המקלדת לערכים המתאימים. ניתן לחלק את הערכים (lexemes) לשתי קבוצות (tokens): מספרים ופעולות מתמטיות, או באנגלית – operators & operands.
השמות של הערכים תואמים לאטריביוט של הלחצנים. לדוגמה, לחצן עם אטריביוט data-token="num_1" תואם לערך 1 וכו׳.
בנוסף, גוף המחשבון מכיל בתוכו שני עצמים נוספים, המסך והמקלדת, כפי שניתן לראות בשורות 22-23.

function Calculator()
{
    this.op_add         = '+';
    this.op_substract   = '-';
    this.op_multiply    = '*';
    this.op_divide      = '/';
    this.op_pow         = '^';
    

    this.num_1      = 1;
    this.num_2      = 2;
    this.num_3      = 3;
    this.num_4      = 4;
    this.num_5      = 5;
    this.num_6      = 6;
    this.num_7      = 7;
    this.num_8      = 8;
    this.num_9      = 9;
    this.num_0      = 0;
    this.num_dot    = '.';
    
    this.screen = new Screen(this);
    this.keys = new Keyboard(this);
}

גוף המחשבון, שהוא גם המוח כפי שנאמר, אחראי גם על התרגום וחישוב התוצאה של המשפט המתמטי אותו הזין המשתמש. השפה המתמטית אותה אנחנו מכירים נקראת infix, משום שהסמלים המייצגים פעולות מתמטיות נמצאים בין המספרים, לדוגמה 1+1. בשביל שהמחשב יוכל לחשב את התוצאה של משפט מתמטי מסויים, יש לתרגם את המשפט ל- postfix, כך שהפעולות המתמטיות נמצאות אחרי המספרים. לדוגמה 11+ מייצג את ה- postfix של המשפט 1+1. אני לא אכנס כאן לעומק העניין ואסביר איך האלגוריתם עובד, אבל אתם מוזמנים להוריד את הקוד ולבחון אותו בעצמכם או לחילופין ליצור איתי קשר בתגובות.

המקלדת

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

/**
 * Keyboard class
 */
function Keyboard( calculator )
{
    this.calculator = calculator;
    this.keys = document.querySelectorAll('.key');
    for( var i = 0; i < this.keys.length; i++ )
    {
        var self = this;
        this.keys[i].onclick = function()
        {
            self.keyPress(this);
        };
    }
}

Keyboard.prototype.keyPress = function( key )
{
    if( key.getAttribute("data-func") === null)
    {
        this.calculator.screen.appendToExpression( key.getAttribute("data-token") );
    }
    else
    {
        this['key_' + key.getAttribute("data-func")]();
    }
};

המסך

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

function Screen( calculator )
{
    this.calculator = calculator;
    this.expressionHolder = document.getElementById('expression');
    this.expression = '';
    this.result = document.getElementById('result');
    this.cursor = new Cursor(this);
}

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

Screen.prototype.appendToExpression = function( token )
{
    var value = this.calculator[token];
    this.expressionHolder.innerHTML += value;
    this.expressionHolder.scrollLeft = this.expressionHolder.scrollWidth;
    this.expression += value;
    this.cursor.moveToEnd();
};

כמובן שהקוד ארוך ומורכב בהרבה ממה שיכולתי להקיף במאמר קצר. אתם מוזמנים לעבור על כל הקוד, הכולל 290 שורות של JavaScript, 200 שורות של CSS ועוד 56 שורות של HTML, ולפנות אלי בכל שאלה בתגובות.