[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
~h6
とul
,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つのイベントがあります.
まず、この記事で紹介するのは、
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
~h6
とul
,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
属性を使用して切り替えを実現したので、イベント処理の面では、あるパターンを見ました!target
のdata
プロパティ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つです走れ!
Reference
この問題について([Clone Project-6]切替ボタンの実装), 我々は、より多くの情報をここで見つけました
https://velog.io/@young_pallete/Clone-Project-7-토글버튼-구현
テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol
Reference
この問題について([Clone Project-6]切替ボタンの実装), 我々は、より多くの情報をここで見つけました https://velog.io/@young_pallete/Clone-Project-7-토글버튼-구현テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol