component - sasjakoning/blok-tech-2022 GitHub Wiki

component

Battle Matcher has several HTML (Handlebars) pages and partials which are controlled by a CSS and Javascript file.

✏️ HTML

The main element of Battle Matcher is the cards you swipe. To get a better idea of it's HTML structure I created a breakdown of the design that I could use to start coding.

1rst iteration 1rst iteration of breakdown sketch

Final iteration final iteration of breakdown sketch

breakdown translated to code:

    {{#each data}}

    <figure class="largeCard">
        <section class="pictureContainer">

            <picture>
                {{!-- profile picture --}}
                <source type="image/webp" srcset="/images/user-image1.webp" />
                <source type="image/jpg" srcset="/images/user-image1.jpg" />
                <img src="/images/user-image1.jpg" alt="" />
            </picture>

            <div class="sliderBtns">
                {{!-- buttons for slider --}}
                <span></span>
                <span></span>
                <span></span>
            </div>

        </section>

        <section class="infoContainer">
            {{!-- distance --}}
            <p class="distance">10km</p>
            {{!-- lvl --}}
            <p class="lvl">5</p>
        </section>




        <figcaption>
            <section class="playerInfo">
                <h2>{{this.charactername}}</h2>
                <h3>{{this.firstname}} {{this.lastname}}</h3>
            </section>

            <section class="tags">
                <p class="playerClass">Rogue</p>
                <p class="playerElement">Shadow</p>
            </section>

            <section class="aboutMe">
                <h3>About me:</h3>
                <p>{{this.aboutme}}</p>
            </section>
        </figcaption>
    </figure>

    {{/each}}

📘 CSS

The entire Battle Matcher feature makes use of a single vanilla CSS file.

Naming

All classnames make use of camelCase for their names.

CSS variables

In my root I defined several variables which I use to set colors and shadows for easy adjusting and a possible dark/light mode in the future.

    :root {
        --background: rgba(233, 228, 239, 1);
        --card-blue: rgb(255, 255, 255);
        --shadow: rgba(38, 7, 63, 0.1);
        --dropshadow: 2px 2px 4px var(--shadow);
        --shadow-hard: rgba(12, 11, 33, 1);

        --detail-yellow: rgb(254, 223, 106);
        --detail-yellow-background: rgba(255, 222, 106, 0.05);
        --detail-green: rgb(112, 207, 152);
        --detail-green-background: rgba(112, 207, 152, 0.05);
        --detail-blue: rgb(39, 133, 254);
        --detail-blue-background: rgba(39, 133, 254, 0.05);
        --detail-red: rgb(255, 98, 112);
        --detail-red-background: rgba(255, 98, 112, 0.05);
        --detail-lightblue: rgb(224, 220, 255);

        --text-color: rgba(38, 7, 63, 1);
        --text-color-alt: rgb(255, 255, 255);
        --text-color-background: rgba(255, 255, 255, 0.05);
    }

Sizing

For sizing I stuck mostly to percentages and em's with a few pixel units for shadows and borders. I chose to use percentages and em because these are easy to use for responsiveness. In my body I set the font-size to 10px so throughout my whole file 1em = 10px.

Positioning

To position my elements I used Flexbox with the occasional use of position: absolute to position icons over elements (ex: in the cards).

💪 Javascript

the client-side uses a single Javascript file to control the feature. The Javascript is written using the ES6 syntax and camelCase.

Syntax changes between ES5 and ES6 (Relevant to this project):

ES5 ES6
var let, const
function(){} arrow function () => {}

code snippet:

    const card = document.querySelector(".largeCard:first-of-type")

    const likeForm = document.querySelector(".form-like")

    likeForm.addEventListener("submit", (e) => {
        console.log("submitted")

        e.preventDefault()

        card.classList.add("cardLike")

        card.addEventListener("animationend", () => {
            likeForm.submit()
        })
    })

The above code listens to a submit (click of a button) and prevents it from reloading the page to first play an animation. after the animation is done the form gets submitted and the page is refreshed.

Progressive Enhancement

To increase the accessibility of Battle Matcher, I applied Progressive Enhancement.

Progressive Enhancement makes sure the user can still use the app even when features such as Javascript aren't available.

Buttons

To like or dislike a card, I use forms and buttons to communicate with the back-end. By sending the user to a new page every time the button is pressed, no Javascript is needed. I only use Javascript to enhance the user experience with animations.

Images

To increase the speed of Battle Matcher I use WebP images. These images uses predictive coding to encode the images, which can decrease file size by 30%. WebP supports transparency which makes it an ideal alternative to PNG files. however, not every browser supports WebP images yet. This is why I decided to make use of the <picture> tag.

The <picture> tag can take different sources(in this case, jpg, png and webp). On load, the user's browser will choose the best match among these sources and display it.

This way, users with newer browsers will be able to enjoy webp images while there is still support for older browsers.

Code snippet:

    <picture>
        {{!-- profile picture --}}
        <source type="image/webp" srcset="/images/user-image1.webp" />
        <source type="image/jpg" srcset="/images/user-image1.jpg" />
        <img src="/images/user-image1.jpg" alt="" />
    </picture>

Navigation

While this element is not progressively Enhanced using javascript, it is fully useable with just css. The navigation element uses a css checkbox trick that enables opening and closing the menu when checking and unchecking the checkbox. Instead of changing the menu icon using Javascript, I changed the background image in CSS when the checkbox is checked.

HTML

    <header>
        <label class="navBtn" for="navCheckbox">
            <input type="checkbox" id="navCheckbox"></input>
            {{!-- open close icon in div --}}
            <div></div>
            <nav class="slideIn">
                <ul>
                    <li>
                        <a href="/">Start Swiping</a>
                    </li>
                    <li>
                        <a href="/matches">View Matches</a>
                    </li>
                    <li>
                        <a href="/reset">Reset Admin User (dev)</a>
                    </li>
                </ul>
            </nav>
        </label>

        <a class="profilePic" href="#"></a>
    </header>

CSS

    .navBtn {
        position: relative;
        margin: 1em;

    }

    .navBtn > div {
        position: absolute;
        width: 2em;
        height: 2em;
        background-image: url(../images/menu-open.svg);
        background-size: contain;
        background-repeat: no-repeat;
        background-position: center;

        z-index: 22;
    }

    .navBtn > input {
        position: absolute;
        z-index: 21;
        transform: scale(2);
        opacity: 0;
    }

    .navBtn > input:checked ~ div {
        background-image: url(../images/menu-close.svg);
    }

    .navBtn > input:checked ~ nav {
        transform: translateX(0);
        background: var(--text-color-alt);
    }

    nav {
        position: fixed;
        top: 0;
        bottom: 0;
        left: 0;
        width: 50%;
        background: var(--text);
        padding: 1rem;
        transform: translateX(-100%);
        transition: .5s;

        z-index: 20;
    }
⚠️ **GitHub.com Fallback** ⚠️