0. SKIN ํ๊ฒฝ ์ดํด ๐ง
๋ด๊ฐ ์ฌ์ฉ ์ค์ธ apricot skin#3์ ์ต์ ์คํจ ๋ต๊ฒ ํ๋ฉด์ด grid
๋ก ์์ฑ๋์ด ์๋ค.
์ ์ฒด ํ๋ฉด์ ๋ํ๋ด๋ wrap
์ด๋ ํด๋์ค๋ ์์ ๊ฐ์ด 4๋ฑ๋ถ ๋์ด์๋ค. ์ด๋ฌํ ํํ๋ฅผ grid๋ผ๊ณ ํ๋ค. grid-template-areas
์์ฑ์ ๋ณด๋ฉด, "header header" "aside main" ๊ณผ ๊ฐ์ด ์ ํ ์๋๋ฐ, ๊ฐ์ ์๋ฐ์ดํ์ ๋ค์ด๊ฐ ์๋ ๋ฉ์ด๋ฆฌ๋ผ๋ฆฌ ๊ฐ์ ํ์ ๋ฐฐ์น๋๋ค. "",""๋ผ๋ฆฌ๋ ์ฐจ๋ก๋๋ก ๋ค์ ํ์ ๋ฐฐ์น๋จ์ ๋ณผ ์ ์๋ค. ์๋ฅผ ๋ณด๋ฉด header๊ฐ 2๋ฒ ์ ํ ๊ฒ์ ๋ณผ ์ ์๋๋ฐ, ์ด๋ ํ ์์ฒด๋ฅผ ์
๋ณํฉ์ฒ๋ผ ๋ค ์ฐจ์งํ๊ฒ ๋ค๋ ๋ป์ด๋ค.
๋ช ๋ฒ์ ์๋ ํ์ผ๋, ์์ ๊ฐ์ด grid๊ฐ ์ง์ฌ์ง ์ํฉ์์ aside ๋ฐ์ ๋ค๋ฅธ ๋ชฉ์ฐจ ์ฉ div๋ฅผ ๋ง๋ค์ด์ ์ด์ฉํ๊ธฐ๋ ์ฝ์ง ์์๋ค. ๋ถ๋ช grid์ aside ์๋ฆฌ์ ์ฌ์ด๋๋ฐ, ๋ชฉ์ฐจ ํตํฉ div๋ฅผ ๋ฃ๋๋ค๋ฉด ํด๊ฒฐ๋ ๋ฌธ์ ์์ง๋ง, ๋๊ณต์ฌ๊ฐ ๋ ๊ฒ ๊ฐ์์, aside ์์ ๋ชฉ์ฐจ๋ฅผ ๋ฃ๋ ๊ฒ์ผ๋ก ๊ฒฐ์ ํ๋ค.
1. import toc-bot CDN ๐ฆ
ํธ๋ฆฌ์ ์๋๋ต๊ฒ side ๋ชฉ์ฐจ๋ฅผ ์ํ CDN ๋ํ ์กด์ฌํ๋ค.
Tocbot CDN
์ฌ๊ธฐ์
<script src="https://cdnjs.cloudflare.com/ajax/libs/tocbot/4.30.0/tocbot.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tocbot/4.30.0/tocbot.css">
์ CDN์ ์ต์ ๋ฒ์ ์ ๊ฐ์ ธ์ค๋ฉด ๋๋ค. CSS๋ฅผ ์ง์ ์ฒ๋ฆฌํ ๋ถ์ CSS๋ ๊ฐ์ ธ์ค์ง ์์๋ ๋๋ค.
๋๋ CSS๋ฅผ ๋ฐ๋ก ๋ง๋ค๊ฑฐ๋ผ script๋ง ์ถ๊ฐํ๋ค.
2. ์ ์ ํ ์์น์ div ํ๊ทธ ๋ฃ๊ธฐ ๐พ
์ด์ ํด๋น CDN์ด ํํ๋ div๋ฅผ ์ ์ ํ ์์น์ ๋ฃ์ด์ค์ผ ํ๋๋ฐ, ๋์ ๊ฒฝ์ฐ 0๋ฒ์์ ํ์ธ ํ๋ฏ์ด Grid ํํ๋ก Sidebar ์์ญ์ด ๋ฐ๋ก ์์ด์ ๊ทธ ๋ถ๋ถ ๋ฐ์ ๋ฃ์ด์ฃผ๋ฉด ๋์๋ค. ๋ค๋ฅธ skin์ ํ์ฉํ๋ ๋ถ๋ค์ f12๋ฅผ ํตํด ์ ์ ํ ์์น๋ฅผ ํ์ธํ๊ณ html ํธ์ง์ ์งํ ํ์๊ธธ ๋ฐ๋๋ค.
<!-- ๋ชฉ์ฐจ (content) -->
<div class="toc-container">
<ul id="toc">
<!-- toc-bot์ ์ด์ฉํ์ฌ ์์ฑ๋๋ ๋ชฉ์ฐจ ํญ๋ชฉ์ด ๋ค์ด๊ฐ -->
</ul>
</div>
์ด์ ์ ul
ํ๊ทธ์ ์์ ํ๊ทธ๋ก h1,h2,h3์ผ๋ก ์ ํ ๋งํฌ๋ค์ด ๋ฌธ์ฅ์ด ๋์ ์ผ๋ก ๋ค์ด๊ฐ ๊ฒ์ด๋ค.
3. ๋์ ๋ชฉ์ฐจ ์์ฑ์ ์ํ javascript ์์ฑ โ๐ป
toc-bot
CDN ๊ณต์ ํํ์ด์ง๋ฅผ ๋ณด๋ฉด,
tocbot.init({
// Where to render the table of contents.
tocSelector: '.js-toc',
// Where to grab the headings to build the table of contents.
contentSelector: '.js-toc-content',
// Which headings to grab inside of the contentSelector element.
headingSelector: 'h1, h2, h3',
// For headings inside relative or absolute positioned containers within content.
hasInnerContainers: true,
});
์์ ๊ฐ์ด ์ ํ์๋ค. ์ธ์๋ฅผ ํ๋์ฉ ์ดํด๋ณด๋ฉด,
tocSelector
: ๋ชฉ์ฐจ๋ก ์ฐ์ด๋ ์์์ ๊ฐ์ฅ ๊ฒ๋ถ๋ถ ํ๊ทธcontentSelector
: ๋ชฉ์ฐจ์ ๋ด์ฉ์ผ๋ก ์ ํ ๋ณธ๋ฌธ์ ๊ฐ์ฅ ๊ฒ๋ถ๋ถ tagheadingSelector
: ๋ชฉ์ฐจ๋ก ํน์ ์ง์ ์์hasInnerContainers
: heading ํ๊ทธ๊ฐ ๋ณธ๋ฌธ ๋ด์ ํน์ ์น์ ์ด๋ ๋ ๋ฆฝ๋ ์ปจํ ์ด๋์ ๋ค์ด๊ฐ ์์ด๋ ์ฐพ์๋ด์ ๋ชฉ์ฐจ์ ์ง์ด๋ฃ์ ๊ฒ์ธ์ง๋ฅผ boolean ๊ฐ์ผ๋ก ๋ฐ๋๋ค. ๊ธฐ๋ณธ๊ฐ์ false์ด๋ค.
๋ด ๊ฒฝ์ฐ์๋ ๋ค์๊ณผ ๊ฐ์ด ๋ ๊ฒ์ด๋ค.
tocbot.init({
tocSelector: '#toc',
contentSelector: '.article-entry',
headingSelector: 'h1, h2, h3',
hasInnerContainers: true
});
์ฌ์ค ์ด๊ฒ๋ง <sript> </script>
ํ๊ทธ ์ฌ์ด์ ๋ฃ์ด๋ ๋ชฉ์ฐจ๊ฐ ๋์ค๊ธด ๋์จ๋ค. ํ์ง๋ง ๋ชฉ์ฐจ๋ฅผ ๋๋ ์ ๋, ํด๋น ์์น๋ก ๊ฐ๋๊ธฐ๋ฅ๊ณผ ํ์ฌ ์ฝ๊ณ ์๋ ์น์
์ ์ ๋ชฉ์ ๋ชฉ์ฐจ์์ ๋ฐ๋ก ํ์ํ๋ ๊ธฐ๋ฅ์ ํํํ์ง ๋ชปํ๋ค. ๋ง์ฝ ํด๋น ๊ธฐ๋ฅ๋ค์ ๋ฃ๊ณ ์ถ๋ค๋ฉด ๋ค์๊ณผ ๊ฐ์ด ์
๊ทธ๋ ์ด๋ ํ๋ฉด ๋๋ค.
4. ์์ฑ๋๋ ๊ธฐ๋ณธ์ ์ธ TOC-BOT
ํ์ธ ๐ง
<!-- ๋ชฉ์ฐจ (content) -->
<div class="toc-container">
<ul id="toc">
<!-- toc-bot์ ์ด์ฉํ์ฌ ์์ฑ๋๋ ๋ชฉ์ฐจ ํญ๋ชฉ์ด ๋ค์ด๊ฐ -->
</ul>
</div>
์์ ๊ฐ์ div๊ฐ ๋ธ๋ก๊ทธ ๊ตฌ๋ ์ ๋ค์๊ณผ ๊ฐ์ด ๋ณํ๋ค.
#toc
์์ .toc-list
, .toc-list-item .is-active-li
,.toc-link node-name--H1
๋ฑ์ด ๋์ด์๋ค. ์ฌ์ค ์ด๊ฑด ๊ตฌํ์ ๋ง์น ํ๋ผ ๊ทธ๋ ์ง, ํ๋์ ํ์ดํ ๋ถ๋ถ์ .is-active-link
๋ผ๋ ํด๋์ค๋ ๋ชจ๋ li ์์ a ํ๊ทธ์ ๋ค์ด๊ฐ๋ค. ์ฆ ๋ชจ๋ ํ๊ทธ๊ฐ active-link ์ทจ๊ธ ๋ฐ๋๊ฒ ๊ธฐ๋ณธ ๊ฐ์ด๋ผ๋ ๊ฒ์ด๋ค.
๋ํ a ํ๊ทธ๊ฐ ๊ฐ๋ฅดํค๋ ์ฃผ์๊ฐ ๋ชจ๋ #
์ผ๋ก ์ฒ๋ฆฌ๋์ด ์์ด. ํด๋น ๋ชฉ์ฐจ๋ฅผ ๋๋ฌ๋ ์ํ๋ ์์น์ ๊ฐ์ง ์๋๋ค. ๋ฐ๋ผ์,
- ๋ชจ๋ a ํ๊ทธ์
.is-active-link
์ง์ฐ๊ธฐ - ๋ชจ๋ a ํ๊ทธ๊ฐ ๊ฐ์ ๊ฐ๋ฆฌํค๋ header์ ์ฃผ์๋ฅผ href๋ก ๊ฐ๋ฅดํค๊ธฐ
๋ฅผ javascript๋ก ๊ตฌํํด์ผํ๋ค. ์ ์ฒด ์ฝ๋์ ๊ทธ์ ๋ํ ์ฃผ์์ผ๋ก ์ค๋ช ์ ๋์ ํ๋ค.
5. ๋์ ํจ๊ณผ๋ฅผ ์ํ javascript ์ ์ฒด ๐จ๐ป
<!-- toc-bot ์คํฌ๋ฆฝํธ ์ถ๊ฐ -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/tocbot/4.30.0/tocbot.min.js" defer></script>
<script>
document.addEventListener("DOMContentLoaded", function() {
// 1) DOM์ ํ์ฉํ์ฌ, ๋ณธ๋ฌธ๊ณผ ๋ชฉ์ฐจ ํ๊ทธ๋ฅผ ํด๋์ค๋ฅผ ํตํด ๊ฐ์ ธ์ค๊ธฐ
const articleEntry = document.querySelector(".article-entry");
const tocContainer = document.querySelector(".toc-container");
// 2) article-entry๊ฐ ์กด์ฌํ๊ณ , h1, h2, h3๊ฐ ์๋ ๊ฒฝ์ฐ์๋ง ๋ชฉ์ฐจ๋ฅผ block์ผ๋ก ๋ณด์ด๊ฒ ํจ.
// tocContainer์ CSS ๊ธฐ๋ณธ๊ฐ์ display: none;์ผ๋ก ์ค์ ํด๋ ์ํ
if (articleEntry && articleEntry.querySelector("h1, h2, h3")) {
tocContainer.style.display = "block";
// 3) ๋ชจ๋ ํค๋ฉ ํ๊ทธ์ ๊ณ ์ ํ ID๋ฅผ ํ ๋น
const headings = articleEntry.querySelectorAll('h1, h2, h3');
headings.forEach((heading, index) => {
if (!heading.id) {
heading.id = 'heading-' + index;
}
});
// 4) ๋ชฉ์ฐจ ์์ฑ
tocbot.init({
tocSelector: '#toc',
contentSelector: '.article-entry',
headingSelector: 'h1, h2, h3',
hasInnerContainers: true
});
// 5) Intersection Observer๋ฅผ ์ด์ฉํ์ฌ ๊ฐ์กฐ ํ๊ทธ ์ ๋ถ ์ง์ฐ๊ณ , ์์น์ ๋ง๊ฒ ์ ์ ํ ์ถ๊ฐ
const tocLinks = document.querySelectorAll('.toc-link');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
// 5-1) ๋ชจ๋ ๋งํฌ์์ ๊ฐ์กฐ ํด๋์ค ์ ๊ฑฐ
tocLinks.forEach(link => link.classList.remove('is-active-link'));
// 5-2) ํ์ฌ ๋ณด์ด๋ ํค๋์ ํด๋นํ๋ ๋งํฌ๋ฅผ ๊ฐ์กฐ
const id = entry.target.getAttribute('id');
const activeLink = document.querySelector(`.toc-link[href="#${id}"]`);
if (activeLink) {
activeLink.classList.add('is-active-link');
}
}
});
}, {
root: null, // ๋ทฐํฌํธ๋ฅผ ๊ธฐ์ค์ผ๋ก ๊ฐ์
threshold: 0.1 // ํค๋๊ฐ 10% ์ด์ ๋ณด์ผ ๋ ๊ฐ์ง
});
headings.forEach((heading) => {
observer.observe(heading);
});
// 6) ๋ชฉ์ฐจ ํญ๋ชฉ ํด๋ฆญ ์ ๋ถ๋๋ฝ๊ฒ ์คํฌ๋กค ์ด๋
tocContainer.addEventListener('click', function(event) {
if (event.target.classList.contains('toc-link')) {
event.preventDefault();
const targetId = event.target.getAttribute('href').substring(1);
const targetElement = document.getElementById(targetId);
if (targetElement) {
window.scrollTo({
top: targetElement.offsetTop,
behavior: 'smooth'
});
}
}
});
} else {
// 7) article-entry๊ฐ ์๊ฑฐ๋ h1, h2, h3๊ฐ ์์ผ๋ฉด ๋ชฉ์ฐจ ์จ๊ธฐ๊ธฐ
// ๋ณธ๋ฌธ๊ธ ์ธ์ ๋ค๋ฅธ ํ์ด์ง์์๋ ์ธ๋ฐ์์ด ๋ชฉ์ฐจ๊ฐ ๋๋๋ง ๋๋ ๊ฒ์ ๋ง๊ธฐ ์ํจ.
if (tocContainer) {
tocContainer.style.display = "none";
}
}
});
</script>
6. CSS ์ถ๊ฐ โ
๋๋ ๋ด skin์ ๋ง๋ ํํ๋ก ๋ง๋ค์๋ค. ๊ฐ์ ์ํ๋ CSS๋ฅผ GPT์ ๋ ผ์ํด ๋ฉ์ง๊ฒ ์์ฑํ๊ธธ ๋ฐ๋๋ค.
/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
/* ์ฌ์ด๋๋ฐ ๋ด ๋ชฉ์ฐจ */
/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.toc-container {
display: none; /* ๊ธฐ๋ณธ์ ์ผ๋ก ์จ๊ฒจ์ง ์ํ, script์์ ํ์ ์ ํ์ */
width: 100%;
margin-top: 20px;
padding: 15px;
background-color: var(--content-background-color);
border-radius: var(--s-border-radius);
box-shadow: 0px 2px 4px 0px var(--shadow-color);
}
.toc-container ul {
list-style: none;
padding-left: 0;
text-align: left;
}
.toc-container li {
margin-bottom: 8px;
font-family: "Pretendard", sans-serif;
}
.toc-container a {
text-decoration: none;
color: var(--font-color);
font-weight: 500;
}
.toc-container a:hover {
text-decoration: underline;
}
.toc-link {
color: #444;
text-decoration: none;
font-weight: 500;
transition: color 0.3s;
}
.toc-link:hover {
color: #000;
text-decoration: underline;
}
.toc-link.is-active-link { /* ํ์ฌ ๋ณด๊ณ ์๋ ๋ชฉ์ฐจ๋ฅผ ๊ฐ์กฐ */
color: #FF6969;
font-weight: bold;
padding-left: 2px;
transition: color 0.3s;
}
7. ๊ฒฐ๊ณผ๋ฌผ โ
8. ์ํ ๐ญ
์๋ชจ์๊ฐ 3์๊ฐ ๋ฐ ์ ๋ ๊ฑธ๋ฆฐ ๊ฒ ๊ฐ๋ค. GPT๋ฅผ ํ์ฉ ํ๋๋ฐ, ์ํ๋ ์๊ตฌ์ฌํญ์ ํ ๋ฒ์ ๋งํ๋ ์คํ๋ ค ์ญํจ๊ณผ๋ง ๋ฌ๋ค. ๋จ๊ณ ๋ณ๋ก ์ฐจ๊ทผ์ฐจ๊ทผ ๋ฌป๋ ๊ฒ์ด ์ฝ๋ ์์ฑ์ ๋์์ด ๋์๋ค. ๋์ ๊ฒฝ์ฐ aside ์์ ๋ชฉ์ฐจ๋ฅผ ๋ฃ์ด์ ๋ชฉ์ฐจ์ css์ position: sticky๋ฅผ ๋ฃ์ง ์์๊ณ , aside์ css์ ์ถ๊ฐํ๋ค.