스터디 과제 - 기능 구현을 목적으로 만든 프로젝트 입니다
- 스티커 만들기 버튼을 누르면 스티커가 생성됩니다
- 스티커가 겹쳐서 생기는 것을 방지하기 위해 계속 다른 위치에 생성합니다. 예를 들어, 항상 우, 하단 10px씩 이동하여 생성합니다
- 생성된 스티커는 드래그로 원하는 위치로 이동할 수 있습니다
- 스티커는 선택하면 항상 가장 위로 올라옵니다
- 각 스티커에서 항목을 추가하는 버튼이 있습니다
- 각 스티커에는 스티커를 삭제하는 버튼이 있습니다
- 항목을 추가하면 스티커에 항목이 생성됩니다
- 항목은 드래그하여 원하는 위치로 이동할 수 있습니다. 같은 스티커 내 뿐만 아니라 다른 스티커로도 이동이 가능합니다
- 각 항목에는 항목을 삭제하는 버튼이 있습니다
- 항목 삭제버튼에 대한 이벤트 핸들링을 해당 버튼에 등록하는 것이 아닌 항목에 등록하여 이벤트 위임방식으로 구현해 주세요
- 생성되는 스티커의 배경색은 스티커 구분을 위해 랜덤으로 생성합니다. 다음의 임의의 색을 사용하면 보기 편한 색상이 지정됩니다
- rgb(150 ~200, 150~200, 150~200)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="./css/common.css" />
<title>STICKER DRAGGABLE PROJECT</title>
</head>
<body>
<button type="button" class="btn" id="sticker-add">스티커 추가</button>
<script type="module" src="./js/app.js"></script>
</body>
</html>
<div class="sticker-wrapper">
<div class="sticker" style="top: 30px; left: 30px;">
<div class="sticker-header">
<h3>STICKER 1</h3>
<div class="sticker-setting">
<button type="button" class="btn" id="sticker-list-add">항목추가</button>
<button type="button" class="btn" id="sticker-remove">스티커삭제</button>
</div>
</div>
<div class="sticker-contents">
<ul class="sticker-list">
<li>
<div>목록1</div>
<button type="button" class="btn" id="sticker-list-remove">삭제</button>
</li>
<li>
<div>목록2</div>
<button type="button" class="btn" id="sticker-list-remove">삭제</button>
</li>
</ul>
</div>
</div>
</div>
* {
margin: 0;
padding: 0;
}
html, body {
height: 100%;
overflow: hidden;
font-size: 12px;
}
.btn {
padding: 3px;
background-color: #fff;
border: 1px solid #cecece;
}
.btn:hover {
box-shadow: 0px 1px 2px -1px #999;
}
.btn:active {
box-shadow: inset 0 1px 2px #999;
}
.sticker-wrapper {
position: relative;
height: 100%;
}
.sticker-wrapper .sticker {
position: absolute;
min-width: 150px;
min-height: 100px;
background-color: #bcf5e0;
cursor: pointer;
}
.sticker-wrapper .sticker .sticker-header {
padding: 5px;
}
.sticker-wrapper .sticker .sticker-header h3 {
margin-bottom: 5px;
}
.sticker-wrapper .sticker .sticker-header .sticker-setting {
display: flex;
justify-content: center;
}
.sticker-wrapper .sticker .sticker-header .sticker-setting button + button {
margin-left: 5px;
}
.sticker-wrapper .sticker .sticker-contents {
padding: 0 5px 5px;
}
.sticker-wrapper .sticker .sticker-contents .sticker-list {
list-style: none;
}
.sticker-wrapper .sticker .sticker-contents .sticker-list li {
display: flex;
flex-wrap: nowrap;
align-items: center;
padding: 5px;
background-color: #fff;
}
.sticker-wrapper .sticker .sticker-contents .sticker-list li + li {
margin-top: 5px;
}
.sticker-wrapper .sticker .sticker-contents .sticker-list li > div {
flex: auto;
}
import StickerLayout from "./stickerLayout.js";
window.addEventListener("DOMContentLoaded", (e) => {
const stickerAdd = document.querySelector("#sticker-add");
const stickerLayout = new StickerLayout({ parentEl: document.body });
stickerAdd.addEventListener("click", (e) => {
stickerLayout.addSticker();
});
});
import Sticker from "./sticker.js";
export default class StickerLayout {
constructor(options) {
this.el = null;
this.count = 1;
this.stickerList = [];
this.initialPositionX = 5;
this.initialPositionY = 5;
Object.assign(this, options);
this.initElement();
this.render();
}
initElement() {
const element = document.createElement("div");
element.classList.add("sticker-wrapper");
element.addEventListener("removeSticker", this.handleClickRemoveSticker.bind(this));
this.el = element;
}
render() {
this.parentEl.appendChild(this.el);
}
addSticker() {
const data = {
id: `sticker_${crypto.randomUUID()}`,
stickerCount: this.count++,
initPosition: {
initX: this.initialPositionX,
initY: this.initialPositionY
},
parentEl: this.el,
parentClientRect: this.el.getBoundingClientRect(),
_self: this
};
const sticker = new Sticker(data);
this.stickerList.push(sticker);
// 아이템 추가 후 초기값에 10을 더해준다
this.initialPositionX = this.initialPositionX + 10;
this.initialPositionY = this.initialPositionY + 10;
}
removeSticker(id) {
this.stickerList = this.stickerList.filter(sticker => sticker.id !== id);
this.count--;
this.initialPositionX = this.initialPositionX - 10;
this.initialPositionY = this.initialPositionY - 10;
}
handleClickRemoveSticker(e) {
const { id } = e.detail;
this.removeSticker(id);
}
}
import StickerList from "./stickerList.js";
export default class Sticker {
constructor(options) {
this.el = null;
this.itemList = [];
this.listCount = 1;
Object.assign(this, options);
this.initElement();
this.render(options.parentEl);
}
initElement() {
const sticker = document.createElement("div");
sticker.classList.add("sticker");
sticker.id = this.id;
const header = this.createHeader();
const contents = this.createContents();
sticker.appendChild(header);
sticker.appendChild(contents);
this.setStyle(sticker);
// DRAG 이벤트 바인딩
sticker.addEventListener("mousedown", this.dragStart.bind(this));
sticker.addEventListener("mouseup", this.dragEnd.bind(this));
// customEvent
sticker.addEventListener("removeList", this.handleClickRemoveList.bind(this));
this.el = sticker;
}
createHeader() {
const header = document.createElement("div");
header.classList.add("sticker-header");
const headingTitle = document.createElement("h3");
headingTitle.textContent = `STICKER ${this.stickerCount}`;
const btnSettings = document.createElement("div");
btnSettings.classList.add("sticker-setting");
const addListBtn = document.createElement("button");
addListBtn.classList.add("btn");
addListBtn.id = "sticker-list-add";
addListBtn.textContent = "항목추가";
addListBtn.addEventListener("click", this.handleClickAdddList.bind(this));
const removeSticker = document.createElement("button");
removeSticker.classList.add("btn");
removeSticker.id = "sticker-remove";
removeSticker.textContent = "스티커삭제";
removeSticker.addEventListener("click", this.handleClickRemoveSticker.bind(this, this.id));
btnSettings.appendChild(addListBtn);
btnSettings.appendChild(removeSticker);
header.appendChild(headingTitle);
header.appendChild(btnSettings);
return header;
}
createContents() {
const contents = document.createElement("div");
contents.classList.add("sticker-contents");
const list = document.createElement("ul");
list.classList.add("sticker-list");
contents.appendChild(list);
return contents;
}
render(parent) {
parent.append(this.el);
}
setStyle(target) {
const bgColor = this.getBgColor();
const { initX, initY } = this.initPosition;
target.style.top = `${initY}px`;
target.style.left = `${initX}px`;
target.style.backgroundColor = `rgba(${bgColor}, 0.8)`;
}
getBgColor() {
const result = [];
for (let i = 0; i < 3; i++) {
result.push(Math.floor(Math.random() * 255));
}
return result.join();
}
addList() {
const data = {
id: `sticker_${crypto.randomUUID()}`,
stickerCount: this.stickerCount,
listCount: this.listCount++,
parentEl: this.el.querySelector(".sticker-list"),
_self: this
}
const list = new StickerList(data);
this.itemList.push(list);
}
removeList(id) {
this.itemList = this.itemList.filter(item => item.id !== id);
}
// DRAG
dragStart(e) {
// 진행중
dragMove(e) {
// 진행중
}
dragEnd(e) {
// 진행중
}
// HANDLER
handleClickAdddList(e) {
this.addList();
}
handleClickRemoveSticker(id) {
const event = new CustomEvent("removeSticker", { bubbles: true, detail: { id } });
this.parentEl.dispatchEvent(event);
this.el.remove();
}
handleClickRemoveList(e) {
const { id } = e.detail;
this.removeList(id);
}
}
export default class StickerList {
constructor(options) {
this.el = null;
Object.assign(this, options);
this.initElement();
this.render();
}
initElement() {
const li = document.createElement("li");
li.id = this.id;
const div = document.createElement("div");
div.textContent = `목록${this.stickerCount}-${this.listCount}`;
const button = document.createElement("button");
button.textContent = "삭제";
button.addEventListener("click", this.handleClickRemove.bind(this, this.id));
li.appendChild(div);
li.appendChild(button);
this.el = li;
}
render() {
this.parentEl.appendChild(this.el);
}
handleClickRemove(id) {
const event = new CustomEvent("removeList", { bubbles: true, detail: { id: id } });
this.parentEl.dispatchEvent(event);
this.el.remove();
}
}
view ex)
지속적으로 업데이트 예정