🍁CSS 조작
자바스크립트로 CSS를 조작하는 방법에는 직접적인 방법과 간접적인 방법이 있다.
모든 태그는 인라인 스타일 시트의 style 속성을 제공하며, 자바스크립트에서도 이러한 style 프로퍼티를 제공한다.
style 프로퍼티로 CSS를 조작하는 직접적인 방법과 class로 CSS를 조작하는 간접적인 방법이 있다.
style 프로퍼티로 조작
기본 코드
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#box1 {
border: 1px solid black;
width: 200px; height: 200px;
}
.one { font-size: 3rem; }
.two { color: cornflowerblue; }
.three { text-decoration: underline; }
</style>
</head>
<body>
<h1>CSS</h1>
<input type="button" value="버튼" id="btn1">
<hr>
<div id="box1">상자</div>
<script>
const btn1 = document.getElementById('btn1');
const box1 = document.getElementById('box1');
</script>
</body>
</html>
BOM에서 구현
btn1.addEventListener('click', function() {
box1.style = 'color: cornflowerblue';
})
btn1.addEventListener('click', function() {
box1.style = 'color: cornflowerblue; font-size: 2rem;';
})
DOM에서 구현
btn1.addEventListener('click', function() {
box1.setAttribute('style', 'color: gold');
})
DOM에서 속성을 조작할 때 setAtrribute를 사용한다.
setAttribute 메서드에 대해서는 위 글을 참고한다.
위와 같이 style 프로퍼티를 수정하여 CSS를 조작할 수 있다. 하지만 이 방법은 프로그램 입장에서 조직적으로 관리하기에는 좋은 방법이 아니다.
조직적인 관리를 위한 하위 프로퍼티
//box.style = 'color: cornflowerblue';
box1.style.color = 'cornflowerblue';
box1.style.fontSize = '2rem';
box1.style.backgroundColor = 'gold';
style 프로퍼티의 접근 가능 속성을 배열로 제공한다. 이는 color와 font-size를 따로 관리할 수 있다는 의미이다.
style 프로퍼티 아래에 하위 프로퍼티가 있는데, 이를 활용하여 CSS를 조직적으로 관리할 수 있다.
CSS에서 사용하는 font-size와 같은 속성명이 자바스크립트에서는 인식할 수 없는 식별자이기 때문에 fontSize와 같이 캐멀 표기법으로 수정하여 사용해야 한다.
map형태의 key값으로 작성
box1.style['color'] = 'gold';
box1.style['font-size'] = '2rem';
box1.style['background-image'] = 'url(../asset/images/dog01.jpg)';
map형태의 key값으로 작성하면 캐멀 표기법이 아닌 원래 CSS 속성의 이름으로 적을 수 있다.
속성값을 작성할 때에는 공통적으로 문자열의 형태로 '' 또는 "" 내부에 작성한다.
읽기 작업 시 주의할 점
console.log('너비: ' + box1.style.width);
상자의 크기를 읽어오는 읽기 작업을 한다고 가정하자.
하지만 너비를 style 속성으로 준 게 아니라 id 선택자로 주었기 때문에 style에서 값을 찾는 건 없는 속성 값을 찾으려는 셈이 된다.
<div id="box1" style="width: 200px;">상자</div>
자바스크립트는 태그에는 접근할 수 있지만, 이처럼 별도의 CSS 접근은 불가능하다.
box1.style.width = '300px';
위와 같이 값을 인라인 스타일 시트에 넣어야 style 속성에 들어가기 때문에 읽기 작업으로 접근할 수 있다.
getComputedStyle
버튼을 클릭하면 상자의 너비를 현재 너비의 50px만큼 증가하는 작업을 한다고 가정해 보자.
<div id="box1" style="width: 200px">상자</div>
btn1.addEventListener('click', function() {
box1.style.width = parseInt(box1.style.width) + 50 + 'px';
})
조금 번거롭지만 위의 방법으로 상자의 너비를 증가하는 작업을 할 수 있다.
자바스크립트 window 객체에 getComputedStyle 메서드를 이용하여 위 작업을 좀 더 쉽게 해 보자.
btn1.addEventListener('click', function() {
const list = window.getComputedStyle(box1);
console.log('너비: ' + list.width);
console.log('높이: ' + list.getPropertyValue('height')); //정석적인 방법
})
getComputedStyle 메서드는 CSS 적용 방식과 상관없이 현재 개체에 적용된 모든 CSS 속성을 가져온다.
현재 개체가 가지고 있는 CSS 속성을 반환하기 때문에 바로 값을 가져오는 게 가능하다.
Style 프로퍼티의 활용
기본 코드
<h1>텍스트</h1>
<div class="tool">
<button type="button" id="btn2"><i class="fa-solid fa-plus"></i></button>
<button type="button" id="btn3"><i class="fa-solid fa-minus"></i></button>
</div>
<p id="content">Lorem ipsum dolor sit amet consectetur adipisicing elit. Explicabo quia suscipit odio sint sapiente quod reprehenderit exercitationem? Quisquam veritatis beatae minima voluptatum ea quasi cum explicabo eveniet animi voluptatem tenetur modi, ratione eligendi obcaecati quae inventore exercitationem enim expedita tempore quam fugiat ipsum. Voluptate obcaecati atque omnis ratione non ab possimus quaerat tenetur placeat. Aliquam illo quas aspernatur rerum placeat officiis adipisci facilis atque dolorem, maxime dolor. Excepturi quo unde provident fugit aspernatur eos inventore quia ea autem, corporis impedit iste totam mollitia, ab facilis labore repellendus tempora reprehenderit, vero numquam molestias omnis. Architecto repellendus placeat, repellat cumque quam sapiente veritatis natus inventore eligendi non doloremque dolorem iure quidem, autem amet provident sequi impedit illo, aperiam sunt. Quos ad soluta amet a nemo iste exercitationem similique laborum cupiditate in dolorem voluptatibus obcaecati accusantium fugiat possimus consectetur, odit provident placeat voluptate? Maxime vel itaque id ad corrupti eum esse veritatis dolore molestiae iusto vero iure nesciunt at animi, nobis vitae facere asperiores expedita blanditiis quae rerum ducimus odio aliquam. Quisquam ea fugiat dignissimos eveniet, qui incidunt accusamus odio, autem, delectus aperiam cumque. Adipisci delectus, incidunt nisi corporis iusto maxime quasi quo eaque mollitia quidem praesentium odit fugiat sint doloribus veritatis qui eos, repellendus porro vero, facere tenetur? Provident vitae, tempora debitis laboriosam dicta sit dolores voluptate explicabo eligendi? Amet nulla fugiat vero voluptatum aspernatur illum! Cupiditate similique dicta, mollitia, ea vitae odio corrupti eius consectetur repellendus reiciendis asperiores. Atque, ipsam natus quas culpa laborum at, repellendus sit ipsum sapiente non ea quo nam autem saepe, odit aspernatur pariatur sequi magnam recusandae repudiandae doloremque alias provident? Nulla neque sit, sed quo repudiandae molestias, ipsum tempore numquam ex cum ratione exercitationem obcaecati dolorem architecto. Reprehenderit voluptatem aut, accusamus animi officia maiores, facilis aliquam similique illo ab veniam, et nisi quam maxime quo velit.</p>
<script>
const btn2 = document.getElementById('btn2');
const btn3 = document.getElementById('btn3');
const content = document.getElementById('content');
</script>
더하기 버튼을 누르면 글자 크기를 크게 하고, 빼기 버튼을 누르면 글자 크기를 작게 하는 작업을 해보자.
let size = 16; //속성 통제를 위한 변수 생성
btn2.addEventListener('click', function() {
size++;
content.style.fontSize = size + 'px';
});
btn3.addEventListener('click', function() {
size--;
content.style.fontSize = size + 'px';
});
브라우저에 따라서 폰트가 최소 단위가 정해져 있기 때문에 일정 수준에 다다르면 폰트 크기가 더 이상 줄어들지 않는다.
class로 조작
- className 프로퍼티
- classList 프로퍼티
btn2.addEventListener('click', function() {
content.className = 'one';
});
btn3.addEventListener('click', function() {
content.className = 'two';
});
class로 CSS를 수정하면 상대방의 CSS를 지워버리고 새로운 CSS로 교체한다.
여러 개의 class 적용
btn2.addEventListener('click', function() {
content.className = 'one two';
});
btn3.addEventListener('click', function() {
content.className + ' three';
});
여러 개의 class를 관리할 때에는 위와 같은 방법을 사용할 수 있지만, 문자열로 조작하는 번거로움이 있기 때문에 class를 배열로 관리하는 classList를 사용한다.
btn2.addEventListener('click', function() {
content.classList.add('one');
content.classList.add('two');
content.classList.add('three');
});
btn3.addEventListener('click', function() {
content.classList.remove('one');
});
class를 추가할 때에는 add 메서드를 사용하며, 반대로 제거할 때에는 remove 메서드를 사용한다.
class의 활용
기본 코드
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.box {
width: 150px;
height: 150px;
/* position: absolute;
left: 0;
top: 0; */
}
#box1 { background-color: beige; }
#box2 { background-color: indianred; }
#box3 { background-color: darksalmon; }
#box4 { background-color: dodgerblue; }
#box5 { background-color: darkslategrey; }
</style>
</head>
<body>
<!-- div#box$.box{상자$}*5 -->
<div id="box1" class="box">상자1</div>
<div id="box2" class="box">상자2</div>
<div id="box3" class="box">상자3</div>
<div id="box4" class="box">상자4</div>
<div id="box5" class="box">상자5</div>
<script>
const list = document.getElementsByClassName('box');
</script>
</body>
</html>
상자의 position(absolute)는 문서 좌측 상단을 기준으로 한다.
마우스를 keydown 할 때의 좌표값을 알아내어 박스를 조작해 보도록 하자.
position과 마우스 이벤트는 위의 글을 참고한다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.box {
width: 150px;
height: 150px;
position: absolute;
left: 0;
top: 0;
}
#box1 { background-color: beige; }
#box2 { background-color: indianred; }
#box3 { background-color: darksalmon; }
#box4 { background-color: dodgerblue; }
#box5 { background-color: darkslategrey; }
</style>
</head>
<body>
<!-- div#box$.box{상자$}*5 -->
<div id="box1" class="box">상자1</div>
<div id="box2" class="box">상자2</div>
<div id="box3" class="box">상자3</div>
<div id="box4" class="box">상자4</div>
<div id="box5" class="box">상자5</div>
<script>
const list = document.getElementsByClassName('box');
let index = 0;
window.onmousedown = function() {
//alert(event.clientX);
list[index].style.left = event.clientX - 75 + 'px';
list[index].style.top = event.clientY - 75 + 'px';
index++;
if (index >= list.length) index = 0;
}
</script>
</body>
</html>
상자가 겹쳤을 때 코딩한 순서대로 보여서 부자연스럽다.
z-index를 수정하여 상자의 출력 순서를 바꿔보도록 하자.
z-index 수정
<script>
const list = document.getElementsByClassName('box');
let index = 0;
let zindex = 1;
window.onmousedown = function() {
//alert(event.clientX);
list[index].style.left = event.clientX - 75 + 'px';
list[index].style.top = event.clientY - 75 + 'px';
list[index].style.zIndex = zindex;
index++;
zindex++;
if (index >= list.length) index = 0;
}
</script>
zindex를 수정하자 조작하는 상자 순서대로 자연스럽게 상위에 출력된다.
🍁CSS 조작 예제
마우스 클릭 위치에 상자 생성
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.box {
width: 150px;
height: 150px;
position: absolute;
left: 0;
top: 0;
}
</style>
</head>
<body>
<script>
window.onmousedown = function() {
if (event.buttons == 1) {
const box = document.createElement('div');
box.className = 'box';
box.style.left = event.clientX - 75 + 'px';
box.style.top = event.clientY - 75 + 'px';
let r = parseInt(Math.random() * 256);
let g = parseInt(Math.random() * 256);
let b = parseInt(Math.random() * 256);
box.style.backgroundColor = `rgb(${r},${g},${b})`
document.body.appendChild(box);
} else if (event.buttons == 2) {
document.body.removeChild(event.target);
}
}
</script>
</body>
</html>
마우스 왼쪽 버튼으로 화면을 클릭하면 클릭한 곳에 상자를 생성되고, 랜덤한 rgb를 부여한다.
그리고 상자 위에서 마우스 오른쪽 버튼을 클릭하면 상자를 제거한다.
Drag & Drop
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.draggable {
position: absolute;
left: 0;
top: 0;
}
.box {
width: 150px;
height: 150px;
background-color: gold;
border: 1px solid #333333;
}
</style>
</head>
<body>
<h1 class="draggable">드래그 앤 드랍</h1>
<div id="box1" class="box draggable">상자1</div>
<div id="box2" class="box draggable">상자2</div>
<div id="box3" class="box draggable">상자3</div>
<script>
let obj; //드래그 객체
let isDown = false;
let x = 0;
let y = 0;
window.onmousedown = function() {
if (event.target.classList.contains('draggable')) {
isDown = true;
x = event.offsetX;
y = event.offsetY;
obj = event.target;
}
};
window.onmousemove = function() {
if (isDown) {
obj.style.left = event.clientX - x + 'px';
obj.style.top = event.clientY - y + 'px';
}
};
window.onmouseup = function() {
isDown = false;
};
</script>
</body>
</html>
움직일 개체가 계속 바뀔 것을 예상하여 개체를 obj 변수에 저장한다.
동시에 여러 개의 개체를 Drag & Drop 할 수 있다.
Ghost image 현상 제거
<img src="../asset/images/dog01.jpg" class="draggable">
event.stopPropagation();
return false;
이미지를 이동하려고 하면 고스트 이미지(미리 보기)가 발생하고 있다.
이는 move가 일어날 때 발생하는 현상이다.
position: relative
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.draggable {
position: absolute;
left: 0;
top: 0;
}
.box {
width: 150px;
height: 150px;
background-color: gold;
border: 1px solid #333333;
}
</style>
</head>
<body>
<h1 class="draggable">드래그 앤 드랍</h1>
<div id="box1" class="box draggable">상자1</div>
<div id="box2" class="box draggable">상자2</div>
<div id="box3" class="box draggable">상자3</div>
<img src="../asset/images/dog01.jpg" class="draggable">
<script>
let obj; //드래그 객체
let isDown = false;
let x = 0;
let y = 0;
window.onmousedown = function() {
if (event.target.classList.contains('draggable')) {
isDown = true;
x = event.offsetX;
y = event.offsetY;
obj = event.target;
}
};
window.onmousemove = function() {
if (isDown) {
obj.style.left = event.clientX - x + 'px';
obj.style.top = event.clientY - y + 'px';
//고스트 이미지 현상 제거
event.stopPropagation();
return false;
}
};
window.onmouseup = function() {
isDown = false;
};
</script>
</body>
</html>
relative를 적용하면 개체가 원래의 위치에서 시작한다.
개체의 크기 resize
Drag & Drop 할 때 처럼 상자를 클릭했을 때 상자의 크기를 줄이거나 늘릴 수 있게 했다.
사각형의 오른쪽 하단 모서리를 선택했을 때 크기를 resize 할 수 있다.
isValid일반 함수에서도 마우스 좌표 정보를 확인할 수 있도록 event를 인자값으로 주었다.
px값을 연산에 사용할 때, 해당 좌표를 parseInt로 'px'를 잘라내야 하는 것에 주의하자.
🦛Drag & Drop 개체 이동 인터페이스
position: absolute
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.box {
width: 150px;
height: 150px;
position: absolute;
left: 0;
top: 0;
transition: left 0.3s ease-out, top 0.3s ease-out; /* 부드러운 이동을 위한 transition */
/* background-color: gold; */
}
#hippo {
font-size: 150px;
color: indigo;
}
</style>
</head>
<body>
<!-- 히포 아이콘을 포함한 박스 -->
<div id="box1" class="box"><i id="hippo" class="fa-solid fa-hippo"></i></div>
<script>
const box1 = document.getElementById('box1');
let isDown;
let prevX, prevY;
let deltaX = 0; // X 방향으로 마지막 이동량 저장
let deltaY = 0; // Y 방향으로 마지막 이동량 저장
let moveAnimationId;
window.onmousedown = function(event) {
if (event.target.id == 'hippo') {
isDown = true;
}
// isDown = true;
prevX = event.clientX;
prevY = event.clientY;
cancelAnimationFrame(moveAnimationId); // 기존 애니메이션 취소
};
window.onmousemove = function(event) {
if (isDown) {
// 마우스 이동량 계산
deltaX = event.clientX - prevX;
deltaY = event.clientY - prevY;
const newLeft = box1.offsetLeft + deltaX;
const newTop = box1.offsetTop + deltaY;
// 박스 위치 업데이트
box1.style.left = newLeft + 'px';
box1.style.top = newTop + 'px';
prevX = event.clientX;
prevY = event.clientY;
event.stopPropagation();
return false;
}
};
window.onmouseup = function() {
isDown = false;
function moveBox() {
const currentLeft = parseFloat(box1.style.left) || 0; // 'px' 단위 처리를 위한 parseFloat
const currentTop = parseFloat(box1.style.top) || 0;
// 마우스 이동 방향으로 박스 이동
const newLeft = currentLeft + deltaX / 10;
const newTop = currentTop + deltaY / 10;
box1.style.left = newLeft + 'px';
box1.style.top = newTop + 'px';
// 이동량을 감소시켜 슬라이딩 효과 생성
deltaX *= 0.9;
deltaY *= 0.9;
// 이동량이 완전히 감소할 때까지 애니메이션 지속
if (Math.abs(deltaX) > 0.1 || Math.abs(deltaY) > 0.1) {
moveAnimationId = requestAnimationFrame(moveBox);
}
}
moveBox();
};
</script>
</body>
</html>
position: relative
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.box {
width: 150px;
height: 150px;
position: relative;
left: 0;
top: 0;
transition: left 0.3s ease-out, top 0.3s ease-out; /* 부드러운 이동을 위한 transition */
/* background-color: gold; */
}
#hippo {
font-size: 150px;
color: indigo;
}
</style>
<script src="https://kit.fontawesome.com/a92e13dcf8.js" crossorigin="anonymous"></script>
</head>
<body>
<!-- 히포 아이콘을 포함한 박스 -->
<div id="box1" class="box"><i id="hippo" class="fa-solid fa-hippo"></i></div>
<script>
const box1 = document.getElementById('box1');
let isDown;
let prevX, prevY;
let deltaX = 0; // X 방향으로 마지막 이동량 저장
let deltaY = 0; // Y 방향으로 마지막 이동량 저장
let moveAnimationId;
window.onmousedown = function(event) {
if (event.target.id == 'hippo') {
isDown = true;
}
// isDown = true;
prevX = event.clientX;
prevY = event.clientY;
cancelAnimationFrame(moveAnimationId); // 기존 애니메이션 취소
};
window.onmousemove = function(event) {
if (isDown) {
// 마우스 이동량 계산
deltaX = event.clientX - prevX;
deltaY = event.clientY - prevY;
const newLeft = box1.offsetLeft + deltaX;
const newTop = box1.offsetTop + deltaY;
// 박스 위치 업데이트
// box1.style.left = newLeft + 'px';
// box1.style.top = newTop + 'px';
box1.style.left = event.clientX - x - box1. getBoundingClientRect().x + 'px';
box1.style.top = event.clientX - y - box1. getBoundingClientRect().y + 'px';
prevX = event.clientX;
prevY = event.clientY;
event.stopPropagation();
return false;
}
};
window.onmouseup = function() {
isDown = false;
function moveBox() {
const currentLeft = parseFloat(box1.style.left) || 0; // 'px' 단위 처리를 위한 parseFloat
const currentTop = parseFloat(box1.style.top) || 0;
// 마우스 이동 방향으로 박스 이동
const newLeft = currentLeft + deltaX / 20;
const newTop = currentTop + deltaY / 20;
box1.style.left = newLeft + 'px';
box1.style.top = newTop + 'px';
// 이동량을 감소시켜 슬라이딩 효과 생성
deltaX *= 0.9;
deltaY *= 0.9;
// 이동량이 완전히 감소할 때까지 애니메이션 지속
if (Math.abs(deltaX) > 0.1 || Math.abs(deltaY) > 0.1) {
moveAnimationId = requestAnimationFrame(moveBox);
}
}
moveBox();
};
</script>
</body>
</html>
마우스를 누르고 있을 때에만 작동되도록 isDown 변수를 만들어서 mousedown일 때 isDown을 true로, mouseup일 때 false로 조건부 실행이 되도록 한다.
마우스를 하마에서 떼면 하마가 마우스 이동 방향으로 미끄러지는 효과를 주었다.
이 소스코드를 이용해 왼쪽 하단에 수족관을 만들고, 물고기를 꺼내 횟집에 던져 주면 횟집에서 회를 떠주는 코드를 구현할 수 있다.🐟