[Clone Project-6]切替ボタンの実装

34208 ワード

開始します。👏


実はすでに完成していましたが、なぜか延期されてしまいました.(...)
つまり、私は以前Reactで書いた日記ブログを守るべきだと思います.このプロジェクトはあまりにも粗雑で、私はメンテナンスに忙しいです.😂
最近また2回コードテストを见て、忙しくて设计モードについての新しい本を见ますが...でもブログを遅らせるわけにはいかない!常に堅持する習慣を身につけなければならない.では、始まります!

本題


まず、この記事で紹介するのは、

1~3をクリックすると、青activeと画像の変更が表示されます.
また、いくつかのbreakpoint以下の場合、個別の切替ボタンが作成されます.

では、まずhtmlを見てみましょう.

index.html

    <section class="about">
        <header class="about__header">
            <h1 class="about__main-copy">우리의 관심은 당신의 <strong>코드</strong></h1>
            <h3 class="about__sub-copy">"디벨로퍼스는 말이죠, 이렇게 도와드려요!"</h3>
        </header>
        <ul class="about__btns">
            <li class="about__btn about__btn--active" data-btn-number="1"></li>
            <li class="about__btn" data-btn-number="2"></li>
            <li class="about__btn" data-btn-number="3"></li>
        </ul>
        <section class="about__features">
            <ul class="about__cards">
                <li class="about__card about__card--active" data-card-number="1">
                    <h4 class="about__card-title" >
                        <div class="about__card-number" data-title-number="1"></div>
                        <span>코드에 집중하는 채용 프로그램</span>
                    </h4>
                    <h5 class="about__card-description">
                        오직 디벨로퍼스에서만 운영하는 데브매칭, 챌린지를 통해 각종 기업의 채용문을 두드릴 수 있어요! 쌓아온 역량을 마음껏 발휘해 원하는 기업의 문을 두드릴 수 있는 기회를 제공한답니다! 😉
                    </h5>
                </li>
                <li class="about__card" data-card-number="2">
                    <h4 class="about__card-title">
                        <div class="about__card-number" data-title-number="2"></div>
                        <span>실전을 위한 트레이닝 시스템</span>
                    </h4>
                    <h5 class="about__card-description">
                        수많은 기업과의 코딩테스트 운영 경험이 그대로 녹아있는 ‘코딩테스트 연습’ 기능과, 챌린지나 데브매칭에 실제로 출제되었던 과제를 풀어보는 ‘과제관' (출시 예정)을 통해 실전 감각을 익혀보아요!
                    </h5>
                </li>
                <li class="about__card" data-card-number="3">
                    <h4 class="about__card-title">
                        <div class="about__card-number" data-title-number="3"></div>
                        <span>코드리뷰 중심의 교육과정</span>
                    </h4>
                    <h5 class="about__card-description">
                        각종 실무 주제를 토대로 운영되는 디벨로퍼스의 온라인 스터디는 단순한 강의를 넘어, 코드리뷰에 중심을 두고 있어요! 상호 피드백을 통한 코드의 개선에 집중하며, 결과적으로 더 나은 코드를 작성해 볼까요?
                    </h5>
                </li>
            </ul>
            <ul class="about__card-images">
                <li class="about__image-box about__image-box--active" data-img-number="1">
                    <img src="https://programmers.co.kr/packs/media/images/img-root-feature-1-67a799d8.png" alt="디벨로퍼스 설명 이미지" class="about__image">
                </li>
                <li class="about__image-box" data-img-number="2">
                    <img src="https://programmers.co.kr/packs/media/images/img-root-feature-2-0705b218.png" alt="디벨로퍼스 설명 이미지" class="about__image">
                </li>
                <li class="about__image-box" data-img-number="3">
                    <img src="https://programmers.co.kr/packs/media/images/img-root-feature-3-37493d41.png" alt="디벨로퍼스 설명 이미지" class="about__image">
                </li>
            </ul>
        </section>
    </section>
なるべく意味を持って書いてみました!
今、h1~h6ul,li,section,article,styles.scssは本当によく知っているような気がします.😉

main.scss


あ、最近はmain.scssと改名しましたが、mainではありません!
なぜなら、この名前の語感がもっと核心的だと思います.
.about {
    @include _setMarginAuto();
    padding-bottom: 8rem;
    &__header {
        display: flex;
        flex-direction: column;
        align-items: center;
        padding-bottom: 4rem;
        strong {
            color: $blue-color;
        }
        .about__main-copy {
            margin: 0;
            font-size: 2rem;
        }
        .about__sub-copy {
            font: {
                size: 1.25rem;
                weight: 400;
            }
            color: darken($program-gray-color, 10%);
        }
    }
    &__btns {
        @include _setPageBtn('about__btn');
        display: none;
        padding: {
            top: 0;
            bottom: 3rem;
        }
    }
    &__features {
        display: flex;
    }
    &__cards,
    &__card-images {
        padding: 0 1rem;
        margin: 0;
        width: 50%;
        box-sizing: border-box;
    }
    &__cards {
        .about__card {
            padding: 1.5rem 1.5rem;
            margin-bottom: 1rem;
            border-radius: 0.5rem;
            box-sizing: border-box;
            transition: all 0.3s;
            &-title {
                margin: 0;
                padding-bottom: 1rem;
                display: flex;
                align-items: center;
                font-size: $font-xl;
                color: darken($program-gray-color, 30%);
            }
            &-number {
                display: flex;
                position: relative;
                top: 0.0625rem;
                justify-content: center;
                align-items: center;
                background: darken($program-gray-color, 30%);
                width: $font-l;
                height: $font-l;
                border-radius: 0.25rem;
                margin-right: 0.5rem;
                &::after {
                    position: relative;
                    content: attr(data-title-number);
                    bottom: 0.0625rem;
                    font-size: 0.875rem;
                    color: white;
                }
            }
            &-description {
                margin: 0;
                font: {
                    size: 1rem;
                    weight: 400;
                }
                line-height: 1.75;
                word-break: keep-all;
            }
            &:hover {
                cursor: pointer;
                background: lighten($program-gray-color, 50%);
            }
            &--active {
                background: lighten($blue-color, 47%);
                .about__card-title {
                    color: $blue-color;
                }
                .about__card-number {
                    background: $blue-color;
                }
                &:hover {
                    background: lighten($blue-color, 47%);
                }
            }
        }
    }
    &__card-images {
        overflow: hidden;
        box-sizing: border-box;
        .about__image-box {
            display: none;
            width: 100%;
            &--active {
                display: block;
            }
            .about__image {
                width: 100%;
            }
        }
    }
    @include customMedia("tablet") {
        &__btns {
            display: flex;
            padding-bottom: 1rem;
            .about__btn {
                margin: 0 0.375rem;
            }
        }
        &__features {
            flex-direction: column;
            align-items: center;
            .about__cards,
            .about__card-images {
                width: auto;
                max-width: 40rem;
            }
            .about__cards {
                .about__card {
                    display: none;
                    background: transparent;
                    &-title {
                        display: flex;
                        justify-content: center;
                    }
                    &-description {
                        text-align: center;
                    }
                    &--active {
                        display: block;
                        background: transparent;
                    }
                    &:hover {
                        background: transparent;
                        cursor: auto;
                    }
                }
            }
        }
    }
    @include customMedia("mobile-and-tablet") {
        &__header {
            padding-bottom: 2rem;
            .about__main-copy {
                font-size: 1.75rem;
            }
            .about__sub-copy {
                font-size: $font-l;
            }
        }
        &__cards {
            padding: 0;
            .about__card {
                &-description {
                    font-size: 0.9375rem;
                }
            }
        }
    }
    @include customMedia("mobile") {
        &__cards {
            .about__card {
                padding: {
                    left: 0;
                    right: 0;
                }
            }
        }
    }
}
いずれにしても反応型に変化するものが多いので、小さな素子でもコードが長い.😂

About.ts(改訂前)


これは修正前に書いたコードです!機能の実現に重点を置いたので、めちゃくちゃ…!
まず、構成部品には2つのイベントがあります.
  • ~タブレット反作用サイズ:クリックボタン切り替え
  • デスクトップ応答サイズ:クリックカード切替
  • そこで、これを実現するために、以下の内容を書きました.
    interface Names{
        [key: string]: string;
    }
    export default class About {
        private readonly names: Names;
        constructor() {
            this.names = {
                aboutBtns: `about__btns`,
                aboutBtn: `about__btn`,
                aboutCards: `about__cards`,
                aboutCard: `about__card`,
                aboutImageBox: `about__image-box`,
            }
            this.handleBtnClick();
            this.handleCardClick();
        }
        handleBtnClick():void {
            const $aboutBtns = document.querySelector(`.${this.names.aboutBtns}`);
            $aboutBtns.addEventListener('click', (e: MouseEvent) => {
                const target = e.target as HTMLElement;
                if (!target.classList.contains(this.names.aboutBtn)) return;
                const idx = target.dataset.btnNumber;
                const $btnElems: NodeListOf<HTMLElement>= document.querySelectorAll(`.${this.names.aboutBtn}`)
                $btnElems.forEach(elem => {
                    elem.classList.toggle(`${this.names.aboutBtn}--active`, e.target === elem);
                });
                const $cardElems: NodeListOf<HTMLElement>= document.querySelectorAll(`.${this.names.aboutCard}`);
                $cardElems.forEach((elem:HTMLElement) => {
                    elem.classList.toggle(`${this.names.aboutCard}--active`, idx === elem.dataset.cardNumber)
                })
                const $imgBoxElems: NodeListOf<HTMLElement>= document.querySelectorAll(`.${this.names.aboutImageBox}`);
                $imgBoxElems.forEach((elem:HTMLElement) => {
                    elem.classList.toggle(`${this.names.aboutImageBox}--active`, idx === elem.dataset.imgNumber)
                })
            })
        }
        handleCardClick() {
            if (window.innerWidth < 991) return;
            const aboutCardArr: NodeListOf<HTMLElement> = document.querySelectorAll(`.${this.names.aboutCard}`);
            const that = this;
            aboutCardArr.forEach((aboutCard:HTMLElement) => {
                aboutCard.addEventListener('click', function (e) {
                    const target = e.currentTarget as HTMLElement;
                    const idx = target.dataset.cardNumber;
                    aboutCardArr.forEach((elem) => {
                        elem.classList.toggle(`${that.names.aboutCard}--active`, elem === this);
                    })
                    const $imgBoxElems: NodeListOf<HTMLElement>= document.querySelectorAll(`.${that.names.aboutImageBox}`);
                    $imgBoxElems.forEach((elem:HTMLElement) => {
                        elem.classList.toggle(`${that.names.aboutImageBox}--active`, idx === elem.dataset.imgNumber)
                    })
                    
                });
            })
        }
    }
    まず重なる部分がたくさんあります
    私の場合、私はdata属性を使用して切り替えを実現したので、イベント処理の面では、あるパターンを見ました!
  • イベントリスナーによる|チェックtargetdataプロパティ
  • 複数のノードでdataキーの属性値が同じノード
  • に切り替わる
    そこで,toggleElementという関数として処理し,再構築する.

    About.ts(修正後)

    interface Names{
        [key: string]: string;
    }
    export default class About {
        private readonly names: Names;
        constructor() {
            this.names = {
                aboutBtns: `about__btns`,
                aboutBtn: `about__btn`,
                aboutCards: `about__cards`,
                aboutCard: `about__card`,
                aboutImageBox: `about__image-box`,
            }
            this.handleBtnClick();
            this.handleCardClick();
        }
        handleBtnClick():void {
            const $aboutBtns = document.querySelector(`.${this.names.aboutBtns}`);
            $aboutBtns.addEventListener('click', (e: MouseEvent) => {
                const target = e.target as HTMLElement;
                if (!target.classList.contains(this.names.aboutBtn)) return;
                const idx = target.dataset.btnNumber;
                this.toggleElement(this.names.aboutBtn, idx, 'btnNumber')
                this.toggleElement(this.names.aboutCard, idx, 'cardNumber');
                this.toggleElement(this.names.aboutImageBox, idx, 'imgNumber');
            })
        }
        handleCardClick():void {0
            // Event Delegation을 시도했으나, e.target이 button이 아닌 하위 요소들이 찍힘. 따라서 각각에 이벤트리스너 넣음.
            const aboutCardArr: NodeListOf<HTMLElement> = document.querySelectorAll(`.${this.names.aboutCard}`);
            aboutCardArr.forEach((aboutCard:HTMLElement) => {
                aboutCard.addEventListener('click', (e: MouseEvent) => {
                    if(window.matchMedia("(max-width: 990px)").matches) return;
                    const target = e.currentTarget as HTMLElement;
                    const idx = target.dataset.cardNumber;
                    this.toggleElement(this.names.aboutCard, idx, 'cardNumber');
                    this.toggleElement(this.names.aboutImageBox, idx, 'imgNumber');
                });
            })
        }
        toggleElement(name: string, idx: string, dataKey:string): void {
            const nodeList = document.querySelectorAll(`.${name}`);
            nodeList.forEach((elem: HTMLElement) => {
                elem.classList.toggle(`${name}--active`, idx === elem.dataset[dataKey]);
            })
        }
    }
    コードがもっと簡単明瞭になったような気がします!△20%近く削減され、方法としてマークされ、機能がより明確になりました.

    終了時🌈


    結局動きが良かったのは上の画像!
    実は、ケラセル以来、私は何も心配することはありません.実はCarrselの後にあっという間に終わっていたのですが….😅
    しかし、私は常に技術を実施するのは簡単だと感じていますが、さらに最適化するのは難しいです.
    ブログプロジェクトを継続するには、メンテナンス作業が必要です.それは、私を成長させているようです.
    このテーマで発表されるのはあと3つです走れ!