패스트캠퍼스에 [30개 프로젝트로 배우는 프론트엔드] 라는 강의가 있어서 듣기 시작했다. 클론코딩인데 듣고 끝! 하게 되면 사실상 나중에 기억이 하나도 남게 되지 않으니까 1프로젝트 1글 정리를 하고자 한다. 첫번째는 가상 키보드 구현하기이다.
module bundler
웹개발에 필요한 html,css,javascript 등을 하나의 파일 또는 여러개의 파일로 병합하거나 압축해주는 역할을 한다.
npm init -y
npm i -D webpack webpack-cli webpack-dev-server
-D 는 dev dependancies로써 설치를 하겠다는 뜻이고
local 개발이나 test 를 설치하는데에만 쓰이는 패키지를 뜻한다.
반면 dependacies 로 설치를 하게 되면
production 환경에서 필요한 패키지를 뜻한다.
개발할 때 필요한 파일들을 생성 해 준다.
npm i -D terser-webpack-plugin //압축 플러그인
npm i -D html-webpack-plugin //html 관련 모듈
npm i -D mini-css-extract-plugin css-loader css-minimizer-webpack-plugin //css 관련 모듈
각각 주석으로 해당에 관한 설명을 적어놨다.
const path = require("path");
const TerserWebpackPlugin = require("terser-webpack-plugin");
const HtmlwebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
module.exports = {
entry: "./src/js/index.js", //js 파일의 진입점
output: {
// 빌드를 했을때 번들파일 관련 속성
filename: "bundle.js", // 파일이름 지정(번들들파일)
path: path.resolve(__dirname, "./dist"), //번들파일이 생성될 경로, path.resolve 메소드를 사용해서 __dirname 을사용해 웹팩이 절대경로를 찾을 수 있도록 해줌
clean: true, //이미 번들파일이 있다면 다 지우고 다시 만들어주는 속성
},
devtool: "source-map", //build 한 파일과 원본 파일을 연결시켜주는 파일
mode: "development", //production 과 devlopment 모드가 있는데 html,css,js 파일을 난독화 기능을 제공하는지에 대한 차이
devServer:{
host:"localhost",
port:8080,
open:true, // dev 서버를 열때 새창을 이용해서 열어줘라
watchFiles: "index.html" //html 변화 감지를 지켜봄, 변화가 있을때마다 reload
},
plugins: [
new HtmlwebpackPlugin({
title: "keyboard", //title
template: "./index.html", //lodash 파일 사용할 수 있게 해줌 -> 유틸성 메소드나 템플릿성 메소드를 제공해 주는 라이브러리
inject: "body", //js 번들했을때 파일을 body 쪽에 넣어주겠다.
favicon: "./favicon.ico",
}),
new MiniCssExtractPlugin({
filename: "style.css",
}),
],
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, "css-loader"], //css파일을 이런 로더를 사용해서 읽어들이도록 하겠다.
},
],
},
optimization: {
//압축해주는 친구들
minimizer: [new TerserWebpackPlugin(), new CssMinimizerPlugin()],
},
};
HtmlwebpackPlugin 플러그인을 설치하고, template: "./index.html"
template 를 index.html 로 설정해 주었다. 이렇게 하면 lodash 파일을 사용할 수 있게 해주는데 index.html 파일에 따로 lodash 문법을 사용해서 적어줘야한다. header
안에 적어줬다.
<head>
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
eslint-config-prettier
플러그인 추가해 준다. eslint-plugin-prettier
는 eslint에 prettier 플러그인을 추가해 주기 위한 패키지이다.npm i -D eslint
npm install --save-dev --save-exact prettier
npm i -D eslint-config-prettier eslint-plugin-prettier
{
"env": {
"browser": true,
"es2021": true
},
"extends": ["eslint:recommended", "plugin:prettier/recommended"],
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"rules": {
"prettier/prettier": "error"
}
}
/node_modules
/dis
webpack.config.js
prettier 홈페이지의 기본 reccommnet 를 가져왔다.
{
"arrowParens": "always",
"bracketSameLine": false,
"bracketSpacing": true,
"embeddedLanguageFormatting": "auto",
"htmlWhitespaceSensitivity": "css",
"insertPragma": false,
"jsxSingleQuote": false,
"printWidth": 80,
"proseWrap": "preserve",
"quoteProps": "as-needed",
"requirePragma": false,
"semi": true,
"singleQuote": false,
"tabWidth": 2,
"trailingComma": "es5",
"useTabs": false,
"vueIndentScriptAndStyle": false
}
/node_modules
/dis
webpack.config.js
{
"name": "playground",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "webpack --mode=production",
"dev": "webpack-dev-server"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"css-loader": "^6.5.1",
"css-minimizer-webpack-plugin": "^3.3.1",
"eslint": "^8.18.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",
"html-webpack-plugin": "^5.5.0",
"mini-css-extract-plugin": "^2.4.6",
"prettier": "2.5.1",
"terser-webpack-plugin": "^5.3.0",
"webpack": "^5.65.0",
"webpack-cli": "^4.9.1",
"webpack-dev-server": "^4.7.2"
}
}
<body>
에 들어 갈 내용<body>
<div class="container" id="container">
<div class="menu">
<label class="switch">
<input id="switch" type="checkbox">
<span class="slider"></span>
</label>
<div class="select-box">
<label for="font">Font:</label>
<select id="font">
<option value="" disabled selected>Choose Font</option>
<option value="Comic Sans MS, Comic Sans, cursive">Font 1</option>
<option value="Arial Narrow, sans-serif">Font 2</option>
<option value="Chalkduster, fantasy">Font 3</option>
</select>
</div>
</div>
<div class="input-group" id="input-group">
<input id="input" class="input" type="text" autocomplete="off">
<div class="error-message">한글 입력 불가</div>
</div>
<div class="keyboard" id="keyboard">
<div class="row">
<div class="key" data-code="Backquote" data-val="`">
<span class="two-value">~</span>
<span class="two-value">`</span>
</div>
<div class="key" data-code="Digit1" data-val="1">
<span class="two-value">!</span>
<span class="two-value">1</span>
</div>
<div class="key" data-code="Digit2" data-val="2">
<span class="two-value">@</span>
<span class="two-value">2</span>
</div>
<div class="key" data-code="Digit3" data-val="3">
<span class="two-value">#</span>
<span class="two-value">3</span>
</div>
<div class="key" data-code="Digit4" data-val="4">
<span class="two-value">$</span>
<span class="two-value">4</span>
</div>
<div class="key" data-code="Digit5" data-val="5">
<span class="two-value">%</span>
<span class="two-value">5</span>
</div>
<div class="key" data-code="Digit6" data-val="6">
<span class="two-value">^</span>
<span class="two-value">6</span>
</div>
<div class="key" data-code="Digit7" data-val="7">
<span class="two-value">&</span>
<span class="two-value">7</span>
</div>
<div class="key" data-code="Digit8" data-val="8">
<span class="two-value">*</span>
<span class="two-value">8</span>
</div>
<div class="key" data-code="Digit9" data-val="9">
<span class="two-value">(</span>
<span class="two-value">9</span>
</div>
<div class="key" data-code="Digit0" data-val="0">
<span class="two-value">)</span>
<span class="two-value">0</span>
</div>
<div class="key" data-code="Minus" data-val="-">
<span class="two-value">_</span>
<span class="two-value">-</span>
</div>
<div class="key" data-code="Equal" data-val="=">
<span class="two-value">+</span>
<span class="two-value">=</span>
</div>
<div class="key back-space-key" data-code="Backspace" data-val="Backspace">
Backspace
</div>
</div>
<div class="row">
<div class="key tab-key">Tab</div>
<div class="key" data-code="KeyQ" data-val="q">Q</div>
<div class="key" data-code="KeyW" data-val="w">W</div>
<div class="key" data-code="KeyE" data-val="e">E</div>
<div class="key" data-code="KeyR" data-val="r">R</div>
<div class="key" data-code="KeyT" data-val="t">T</div>
<div class="key" data-code="KeyY" data-val="y">Y</div>
<div class="key" data-code="KeyU" data-val="u">U</div>
<div class="key" data-code="KeyI" data-val="i">I</div>
<div class="key" data-code="KeyO" data-val="o">O</div>
<div class="key" data-code="KeyP" data-val="p">P</div>
<div class="key" data-code="BracketLeft" data-val="[">
<span class="two-value">{</span>
<span class="two-value">[</span>
</div>
<div class="key" data-code="BracketRight" data-val="]">
<span class="two-value">}</span>
<span class="two-value">]</span>
</div>
<div class="key back-slash-key" data-code="Backslash" data-val="\">
<span class="two-value">|</span>
<span class="two-value">\</span>
</div>
</div>
<div class="row">
<div class="key caps-lock-key">CapsLock</div>
<div class="key" data-code="KeyA" data-val="a">A</div>
<div class="key" data-code="KeyS" data-val="s">S</div>
<div class="key" data-code="KeyD" data-val="d">D</div>
<div class="key" data-code="KeyF" data-val="f">F</div>
<div class="key" data-code="KeyG" data-val="g">G</div>
<div class="key" data-code="KeyH" data-val="h">H</div>
<div class="key" data-code="KeyJ" data-val="j">J</div>
<div class="key" data-code="KeyK" data-val="k">K</div>
<div class="key" data-code="KeyL" data-val="l">L</div>
<div class="key" data-code="Semicolon" data-val=";">
<span class="two-value">:</span>
<span class="two-value">;</span>
</div>
<div class="key" data-code="Quote" data-val="'">
<span class="two-value">"</span>
<span class="two-value">'</span>
</div>
<div class="key enter-key" data-code="Enter">Enter</div>
</div>
<div class="row">
<div class="key left-shift-key" data-code="ShiftLeft">Shift</div>
<div class="key" data-code="KeyZ" data-val="z">Z</div>
<div class="key" data-code="KeyX" data-val="x">X</div>
<div class="key" data-code="KeyC" data-val="c">C</div>
<div class="key" data-code="KeyV" data-val="v">V</div>
<div class="key" data-code="KeyB" data-val="b">B</div>
<div class="key" data-code="KeyN" data-val="n">N</div>
<div class="key" data-code="KeyM" data-val="m">M</div>
<div class="key" data-code="Comma" data-val=",">
<span class="two-value">
<
</span>
<span class="two-value">,</span>
</div>
<div class="key" data-code="Period" data-val=".">
<span class="two-value">
>
</span>
<span class="two-value">.</span>
</div>
<div class="key" data-code="Slash" data-val="/">
<span class="two-value">?</span>
<span class="two-value">/</span>
</div>
<div class="key right-shift-key" data-code="ShiftRight">Shift</div>
</div>
<div class="row">
<div class="key fn-key">Ctrl</div>
<div class="key fn-key">-</div>
<div class="key fn-key">Alt</div>
<div class="key space-key" data-code="Space" data-val="Space">Space</div>
<div class="key fn-key">Alt</div>
<div class="key fn-key">Fn</div>
<div class="key fn-key">-</div>
<div class="key fn-key">Ctrl</div>
</div>
</div>
</div>
</body>
* {
user-select: none;
outline: none;
}
html[theme="dark-mode"] {
/* ! */
filter: invert(100%) hue-rotate(180deg);
}
body {
background-color: white;
}
.container {
width: 1050px;
margin: auto;
}
.keyboard {
background-color: gray;
color: gray;
width: 1050px;
border-radius: 4px;
}
.row {
/* ! */
display: flex;
}
.key {
width: 60px;
height: 60px;
margin: 5px;
border-radius: 4px;
background-color: white;
cursor: pointer;
/* ! */
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
transition: 0.2s;
/* ! */
}
.key:hover {
background-color: lightgray;
}
.key.active {
background-color: #333;
color: #fff;
}
.key .two-value {
width: 100%;
text-align: center;
}
.fn-key {
width: 80px;
}
.space-key {
width: 420px;
}
.back-space-key {
width: 130px;
}
.tab-key {
width: 95px;
}
.back-slash-key {
width: 95px;
}
.caps-lock-key {
width: 110px;
}
.left-shift-key {
width: 145px;
}
.enter-key {
width: 150px;
}
.right-shift-key {
width: 185px;
}
.menu {
/* ! */
display: flex;
}
.switch {
position: relative;
width: 60px;
height: 34px;
}
.switch input {
display: none;
}
.slider {
top: 0;
left: 0;
right: 0;
bottom: 0;
position: absolute;
cursor: pointer;
background-color: gray;
border-radius: 34px;
transition: 0.4s;
}
/* ! */
.slider::before {
position: absolute;
content: "";
height: 26px;
width: 26px;
left: 4px;
bottom: 4px;
background-color: white;
transition: 0.5s;
border-radius: 50%;
}
input:checked + .slider {
background-color: black;
}
input:checked + .slider::before {
/* ! */
transform: translateX(26px);
}
.select-box {
position: relative;
margin-left: 60px;
height: 34px;
}
.select-box select {
/* ! */
font-size: 0.9rem;
/* ! */
padding: 2px 5px;
height: 34px;
width: 200px;
}
.input-group {
margin: 100px 0px;
}
.input {
border: none;
border-bottom: 2px solid lightgrey;
width: 1050px;
height: 50px;
font-size: 30px;
text-align: center;
display: block;
}
.error-message {
color: #cc0033;
font-size: 30px;
line-height: 30px;
margin-top: 10px;
text-align: center;
}
.input-group .error-message {
display: none;
}
.error input {
border-bottom: 2px solid red;
}
.error .error-message {
display: block;
}
javascript 클래스로 만들어주었다.
이벤트 목록
1. 다크 모드 테마
2. 폰트 변경
3. 키보드로 작성 가능 (keydown, keyup)
4. 화면 가상 키보드 클릭 시 작성 가능 (mousedown, mouseup)
class 의 속성(property)들은 기본적으로 public 하며 class 외부에서 읽히고 수정될 수 있다. 하지만, ES2019 에서는 해쉬 # prefix
를 추가해 private class 필드를 선언할 수 있게 되었다.
export class Keyboard {
#swichEl;
#fontSelectEl;
#containerEl;
#keyboardEl;
#inputGroupEl;
#inputEl;
#keyPress = false;
#mouseDown = false;
constructor() {
this.#assignElement();
this.#addEvent();
}
#assignElement() {
this.#containerEl = document.getElementById("container");
this.#swichEl = this.#containerEl.querySelector("#switch");
this.#fontSelectEl = this.#containerEl.querySelector("#font");
this.#keyboardEl = this.#containerEl.querySelector("#keyboard");
this.#inputGroupEl = this.#containerEl.querySelector("#input-group");
this.#inputEl = this.#inputGroupEl.querySelector("#input");
}
#addEvent() {
this.#swichEl.addEventListener("change", this.#onChageTheme);
this.#fontSelectEl.addEventListener("change", this.#onChageFont);
document.addEventListener("keydown", this.#onKeyDown.bind(this));
document.addEventListener("keyup", this.#onKeyUp.bind(this));
this.#inputEl.addEventListener("input", this.#onInput);
this.#keyboardEl.addEventListener(
"mousedown",
this.#onMouseDown.bind(this)
);
document.addEventListener("mouseup", this.#onMouseUp.bind(this));
}
#onMouseUp(event) {
if (this.#keyPress) return;
this.#mouseDown = true;
const keyEl = event.target.closest("div.key");
const isActive = !!keyEl?.classList.contains("active");
const val = keyEl?.dataset.val;
if (isActive && !!val && val !== "Space" && val !== "Backspace") {
this.#inputEl.value += val;
}
if (isActive && val === "Space") {
this.#inputEl.value += " ";
}
if (isActive && val === "Backspace") {
this.#inputEl.value = this.#inputEl.value.slice(0, -1);
}
this.#keyboardEl.querySelector(".active")?.classList.remove("active");
}
#onMouseDown(event) {
if (this.#keyPress) return;
this.#mouseDown = true;
event.target.closest("div.key")?.classList.add("active");
}
#onInput(event) {
event.target.value = event.target.value.replace(/[ㄱ-ㅎ|ㅏ-ㅣ|가-힣]/, "");
}
#onKeyDown(event) {
if (this.#mouseDown) return;
this.#keyPress = true;
this.#keyboardEl
.querySelector(`[data-code=${event.code}]`)
?.classList.add("active");
this.#inputGroupEl.classList.toggle(
"error",
/[ㄱ-ㅎ|ㅏ-ㅣ|가-힣]/.test(event.key)
);
}
#onKeyUp(event) {
if (this.#mouseDown) return;
this.#keyPress = false;
this.#keyboardEl
.querySelector(`[data-code=${event.code}]`)
?.classList.remove("active");
}
#onChageTheme(event) {
document.documentElement.setAttribute(
"theme",
event.target.checked ? "dark-mode" : ""
);
}
#onChageFont(event) {
document.body.style.fontFamily = event.target.value;
}
}
강의를 들으며 알고는 있었지만 정확하게 개념이 부족한 부분들은 따로 공부가 필요했다. 아래의 개념들은 이론노트 블로그에 따로 정리 해 두겠다. 필요한 분들 (미래의 나포함) 은 링크를 타고 가서 보면 좋을 것 같다.
- bind()
- this
물론 혼자 공부하고 혼자 개발 해 보는 것도 중요하지만 강의를 듣게 되면 좋은 점 중에 하나는 나보다 더 잘하는 사람들의 코드를 보며 '저렇게 코드를 짜는구나'라는 방향성이 생긴다. 자바스크립트를 클래스로 사용해서 짜본 적이 없는데 새로운 개념도 얻게 되는 이점이 있다. 물론 모르는 개념들은 따로 찾아보고 정리해야 베스트이지만 말이다.
강의듣고 따라치는 것은 쉽지만 사실상 정리하려고 보면 엄두가 나지 않는다. 지금 이 글 정리도 2주나 지나서야 작성을 완료했다.🤣 하지만 글로 정리해 두면 확실히 내것이 되는 것이 있다.
일전에 퍼블리싱을 배울때에도 오프라인에서 강의를 듣고 직접 손으로 손코딩을 해가며 정리를 했던 기억이 있는데 시간이 오래걸려 비효율적인 것 같았지만서도 퍼블리싱 실무를 할때에는 확실히 그때의 손코딩하며 익혔던 개념들을 떠올리면서 작업을 하게 된다. 정리의 힘을 누구보다 잘 깨닫고 느껴 아주 운이 좋다고 생각한다.