CSS nth-child 이용해서 랜덤처럼 보이게 구현하기
사실 자바스크립트를 이용하면 간단하게 랜덤을 구현할 수 있다.
하지만 아래 두 가지를 만족하는 상태에서 각 요소에 대해 랜덤을 구현하는것은 까다롭다.
javscript 코드 변경 금지요소가 얼마나 만들어질지 모름
아래는, 순수하게 css만 이용하여 랜덤하게 보이도록 구현하는 방법이다.
1. 랜덤하게 보이도록 구현하기
우선 부모가
.container, 자식이 모두.content클래스명을 가지며, 자식이 얼마나 추가될지 모르는 상태로 가정
<div class="container">
<div class="content"></div>
<div class="content"></div>
<div class="content"></div>
</div>
아래 예시와 같이 scss에서 반복문으로 nth-child를 돌리면 랜덤이 가능하다.
하지만 요소의 최대 개수를 지정해줘야 하기 때문에, 이 값을 초과했을 경우 (예시에서는 11번째 요소부터) 랜덤이 적용되지 않는다.
@for $i from 1 through 10 {
.content:nth-child(#{$i}) {
background-color: rgb(random(256), random(256), random(256));
}
}
그렇다고 최대 개수를 너무 높게 지정하게 되면 컴파일된 css 파일의 크기가 커지기 때문에 비효율적이다.
이때 nth-child의 함수형 표기법을 이용하면 요소 개수만큼만 적용할 수 있는데, nth-child의 인자로 숫자 대신 An+B 형식으로 넣어주면 된다.
예시 : [n -> 0, 1, 2 ...]
:nth-child(n): 0, 1, 2, 3, ...:nth-child(2n): 0, 2, 4, 6, ...:nth-child(3n+1): 1, 4, 7, 10, ...:nth-child(-2n+7): 7, 5, 3, 1:nth-child(n+3):nth-child(-n+6): 3, 4, 5, 6
요소 개수에 맞게 css가 적용이 되긴 하지만, 하나만 적용한다면 규칙적인 모습을 가지게 된다.
이를 해결하려면 n, 2n, 3n, ... 이렇게 여러개를 적용해주면 된다. 그 수가 많아질수록 더 랜덤해 보이는 결과가 나타난다.
.content:nth-child(n) {
background-color: red;
}
.content:nth-child(2n) {
background-color: green;
}
.content:nth-child(3n) {
background-color: blue;
}
.content:nth-child(4n) {
background-color: black;
}
.content:nth-child(5n) {
background-color: orange;
}
.content:nth-child(6n) {
background-color: violet;
}
.content:nth-child(7n) {
background-color: cyan;
}
.content:nth-child(8n) {
background-color: yellow;
}
.content:nth-child(9n) {
background-color: gray;
}
.content:nth-child(10n) {
background-color: white;
}
이렇게 되면, nth-child 인자들의 최소공배수 이후에 패턴이 반복되게 된다.
1~10까지의 최소공배수, lcm(1,2,3,4,5,6,7,8,9,10) -> 즉 2520번 이후 같은 패턴이 반복되게 된다.
만약 아래와 같이 소수를 활용한다면 같은 양의 코드로 패턴의 길이를 훨씬 늘릴 수 있다.
(또는 같은 길이의 패턴에 코드의 양을 줄일 수 있다.)
2. 소수를 사용하여 효율성 최대화
매미는 수명이 소수인 5, 7, 13, 17년으로, 천적과 마주칠 확률을 낮춘다고 한다.
- 소수와 자연수의 최소공배수는 두 수의 곱 (자연수가 해당 소수의 배수가 아닐 경우)
수명이 천적
4년, 매미5년이면, 최소20년주기로 한 번씩 마주친다.
수명 이 천적
6년, 매미17년이면, 최소102년주기로 한 번씩 마주친다.
또한 매미끼리도 서로 먹이 경쟁을 같이 할 확률을 낮춘다고 한다.
- 서로소일 경우, 최소공배수는 두수의 곱
매미1
5년, 매미27년이면, 최소35년주기로 한 번씩 먹이 경쟁을 한다.
매미1
13년, 매미217년이면, 최소221년주기로 한 번씩 먹이 경쟁을 한다.
이 원리를 css에 적용하면, 같은 양의 코드에서 패턴의 길이를 최대로 가져갈 수 있다.
3. 코드
단순히 부모 요소인 .container 내부에 .content 자식을 반복문으로 추가하는 html, js 코드이다.
<div class="container"></div>
const container = document.querySelector('.container');
for (let i = 0; i < 1000; i++) {
const content = document.createElement('div');
content.classList.add('content');
container.appendChild(content);
}
nth-child 인자로 소수를 사용한 css 코드이다.
.content {
float: left;
width: 13px;
height: 13px;
}
.content:nth-child(n) {
background-color: red;
}
.content:nth-child(2n) {
background-color: green;
}
.content:nth-child(3n) {
background-color: blue;
}
.content:nth-child(5n) {
background-color: black;
}
.content:nth-child(7n) {
background-color: orange;
}
.content:nth-child(11n) {
background-color: violet;
}
.content:nth-child(13n) {
background-color: cyan;
}
.content:nth-child(17n) {
background-color: yellow;
}
.content:nth-child(19n) {
background-color: gray;
}
.content:nth-child(23n) {
background-color: white;
}
만약 scss를 사용할 경우 아래와 같이 사용하면 되며, 색상을 랜덤하게 사용할 수도 있다.
.content {
float: left;
width: 13px;
height: 13px;
}
$prime_num: 1, 2, 3, 5, 7, 11, 13, 17, 19, 23;
@for $i from 1 through length($prime_num) {
.content:nth-child(#{nth($prime_num, $i)}n) {
background-color: rgb(random(256), random(256), random(256));
}
}
이 경우, 소수는 모두 서로소이기 때문에 최소공배수를 구할때 모두 곱해주면 된다.
lcm(1,2,3,5,7,11,13,17,19) -> 즉 223,092,870번 이후 같은 패턴이 반복되게 된다.
같은 10줄의 코드라도 소수를 활용하였을때 훨씬 효율적이라는 것을 알 수 있다.
전체 코드
<!DOCTYPE html>
<html>
<head>
<title>css 랜덤 예제</title>
<meta charset="UTF-8" />
<style>
body {
background-color: lightgray;
}
.content {
float: left;
width: 10px;
height: 10px;
}
.content:nth-child(n) {
background-color: red;
}
.content:nth-child(2n) {
background-color: green;
}
.content:nth-child(3n) {
background-color: blue;
}
.content:nth-child(5n) {
background-color: black;
}
.content:nth-child(7n) {
background-color: orange;
}
.content:nth-child(11n) {
background-color: violet;
}
.content:nth-child(13n) {
background-color: cyan;
}
.content:nth-child(17n) {
background-color: yellow;
}
.content:nth-child(19n) {
background-color: gray;
}
.content:nth-child(23n) {
background-color: white;
}
</style>
</head>
<body>
<div class="container"></div>
</body>
<script>
const container = document.querySelector('.container');
for (let i = 0; i < 1000; i++) {
const content = document.createElement('div');
content.classList.add('content');
container.appendChild(content);
}
</script>
</html>
실행 결과
function App() { const html = ` <!DOCTYPE html> <html> <head> <title>css 랜덤 예제</title> <meta charset="UTF-8" /> <style> body { background-color: lightgray; } .content { float: left; width: 11px; height: 11px; } .content:nth-child(n) { background-color: red; } .content:nth-child(2n) { background-color: green; } .content:nth-child(3n) { background-color: blue; } .content:nth-child(5n) { background-color: black; } .content:nth-child(7n) { background-color: orange; } .content:nth-child(11n) { background-color: violet; } .content:nth-child(13n) { background-color: cyan; } .content:nth-child(17n) { background-color: yellow; } .content:nth-child(19n) { background-color: gray; } .content:nth-child(23n) { background-color: white; } </style> </head> <body> <div class="container"></div> </body> <script> const container = document.querySelector(".container"); for (let i = 0; i < 4018; i++) { const content = document.createElement("div"); content.classList.add("content"); container.appendChild(content); } </script> </html> `; return ( <div> <iframe srcDoc={html} style={{ width: '100%', height: '35em' }}></iframe> </div> ); }
참고자료
- https://www.the215guys.com/learning/nth-child-cicada-principle/
- https://devopedia.org/cicada-principle
- https://developer.mozilla.org/ko/docs/Web/CSS/:nth-child
- https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=ysuny2&logNo=20182947888
- https://www.sitepoint.com/the-cicada-principle-and-why-it-matters-to-web-designers/