[CSS, JS] 노트, 원고지 만들기

개발

[CSS, JS] 노트, 원고지 만들기
최종 수정일:

노트

HTML

<div class="note">
    <p>
        오늘도 또 우리 수탉이 막 쫓기었다. 내가 점심을 먹고 나무를 하러 갈
        양으로 나올 때이었다. 산으로 올라서려니까 등뒤에서 푸르득푸드득, 하고
        닭의 횃소리가 야단이다. 깜짝 놀라서 고개를 돌려보니 아니나다르랴, 두
        놈이 또 얼리었다.
    </p>
    <p>
        점순네 수탉(은 대강이가 크고 똑 오소리같이 실팍하게 생긴 놈)이 덩저리
        작은 우리 수탉을 함부로 해내는 것이다. 그것도 그냥 해내는 것이 아니라
        푸드득 하고 면두를 쪼고 물러섰다가 좀 사이를 두고 또 푸드득 하고
        모가지를 쪼았다. 이렇게 멋을 부려 가며 여지없이닦아 놓는다. 그러면 이
        못생긴 것은 쪼일 적마다 주둥이로 땅을 받으며 그 비명이 킥, 킥할 뿐이다.
        물론 미처 아물지도 않은 면두를 또 쪼이어 붉은 선혈은 뚝뚝 떨어진다.
    </p>
    <img src="https://i.imgur.com/pEmnc36.jpg" alt="roster" />
    <p>
        이걸 가만히 내려다보자니 내 대강이가 터져서 피가 흐르는 것같이 두 눈에서
        불이 번쩍난다. 대뜸 지게 막대기를 메고 달려들어 점순네 닭을 후려칠까
        하다가 생각을 고쳐먹고헛매질로 떼어만 놓았다.
    </p>
    <p>
        이번에도 점순이가 쌈을 붙여 놨을 것이다. 바짝바짝 내 기를 올리느라고
        그랬음에 틀림없을 것이다.
    </p>
    <p>
        고놈의 계집애가 요새로 들어서서 왜 나를 못 먹겠다고 고렇게
        아르렁거리는지 모른다.
    </p>
    <p>나흘 전 감자 조각만 하더라도 나는 저에게 조금도 잘못한 것은 없다.</p>
</div>

.note를 생성하고 아래에 텍스트와 이미지를 추가했습니다.
외에도 다양한 요소를 추가하실 수 있지만, 높이나 너비를 잘 고려하셔야 합니다.

CSS

:root {
    --bg-color: #fff;
    --bg-transparent: rgba(255, 255, 255, 0);
    --border-color: #2035a7;
}
 
.note {
    font-size: 16px;
    line-height: 32px;
    border: 2px solid var(--border-color);
    background: -webkit-linear-gradient(
        top,
        var(--border-color) 0%,
        var(--border-color) 1px,
        var(--bg-transparent) 1px,
        var(--bg-transparent) 100%
    );
    background: linear-gradient(
        to bottom,
        var(--border-color) 0%,
        var(--border-color) 1px,
        var(--bg-transparent) 1px,
        var(--bg-transparent) 100%
    );
    background-size: 100% 32px;
}
 
.note > p {
    margin: 0;
    padding: 0 4px;
}
 
.note > img {
    display: block;
    width: auto;
    max-width: 100%;
    margin: 0 auto;
    object-fit: cover;
}

노트 제작은 굉장히 간단한 편입니다.
1px짜리 줄 하나 긋는 그래디언트를 만든 뒤, 32px마다 반복되게 해뒀습니다.

아래에 들어갈 요소들의 높이만 전부 신경 써준다면 노트에 쓰는 느낌으로 글을 쓸 수 있습니다.

JS

function resizeImageForNote() {
    const note = document.querySelectorAll(".note");
    const resize = () => note.forEach(resizeImage);
 
    resize();
    window.addEventListener("load", resize, { passive: true });
    window.addEventListener("resize", resize, { passive: true });
}
 
function resizeImage(element) {
    element.querySelectorAll("img").forEach((img) => {
        const { naturalWidth, naturalHeight } = img;
        const ratio = naturalHeight / naturalWidth;
        const newHeight = element.offsetWidth * ratio;
 
        img.height = Math.floor(newHeight - (newHeight % 32));
    });
}
 
resizeImageForNote();

이미지처럼 일정 비를 유지하며 작아져야 하는 요소를 위한 예제입니다.
CSS에서 줄 간격을 변경하시면 newHeight % 32의 32도 그 값으로 변경해주셔야 합니다.

결과

원고지

HTML

<div class="manuscript-container">
    <div class="manuscript">
        <p>
            오늘도 또 우리 수탉이 막 쫓기었다. 내가 점심을 먹고 나무를 하러 갈
            양으로 나올 때이었다. 산으로 올라서려니까 등뒤에서 푸르득푸드득,
            하고 닭의 횃소리가 야단이다. 깜짝 놀라서 고개를 돌려보니
            아니나다르랴, 두 놈이 또 얼리었다.
        </p>
        <p>
            점순네 수탉(은 대강이가 크고 똑 오소리같이 실팍하게 생긴 놈)이
            덩저리 작은 우리 수탉을 함부로 해내는 것이다. 그것도 그냥 해내는
            것이 아니라 푸드득 하고 면두를 쪼고 물러섰다가 좀 사이를 두고 또
            푸드득 하고 모가지를 쪼았다. 이렇게 멋을 부려 가며 여지없이닦아
            놓는다. 그러면 이 못생긴 것은 쪼일 적마다 주둥이로 땅을 받으며 그
            비명이 킥, 킥할 뿐이다. 물론 미처 아물지도 않은 면두를 또 쪼이어
            붉은 선혈은 뚝뚝 떨어진다.
        </p>
        <img src="https://i.imgur.com/pEmnc36.jpg" alt="roster" />
        <p>
            이걸 가만히 내려다보자니 내 대강이가 터져서 피가 흐르는 것같이 두
            눈에서 불이 번쩍난다. 대뜸 지게 막대기를 메고 달려들어 점순네 닭을
            후려칠까 하다가 생각을 고쳐먹고헛매질로 떼어만 놓았다.
        </p>
        <p>
            이번에도 점순이가 쌈을 붙여 놨을 것이다. 바짝바짝 내 기를 올리느라고
            그랬음에 틀림없을 것이다.
        </p>
        <p>
            고놈의 계집애가 요새로 들어서서 왜 나를 못 먹겠다고 고렇게
            아르렁거리는지 모른다.
        </p>
        <p>나흘 전 감자 조각만 하더라도 나는 저에게 조금도 잘못한 것은 없다.</p>
    </div>
</div>

.note와 마찬가지지만 줄 공책과 달리 원고는 한 칸의 크기가 정해져 있어 .manuscript의 크기도 유동적으로 변경되어야 하기에, .manuscript를 감싸주는 .manuscript-container가 하나 추가되었습니다.

CSS

:root {
    --bg-color: #fff;
    --bg-transparent: rgba(255, 255, 255, 0);
    --border-color: #d12e2e;
}
 
.manuscript-container {
    margin: 0 auto;
}
 
.manuscript {
    font-size: 16px;
    line-height: 32px;
    padding: 8px 0;
    border: 2px solid var(--border-color);
    background: -webkit-linear-gradient(
            top,
            var(--bg-transparent) 0%,
            var(--bg-transparent) 1px,
            var(--bg-color) 1px,
            var(--bg-color) 8px,
            var(--bg-transparent) 8px,
            var(--bg-transparent) 100%
        ),
        -webkit-linear-gradient(
            top,
            var(--bg-transparent) 0%,
            var(--bg-transparent) 1px,
            var(--bg-color) 1px,
            var(--bg-color) 8px,
            var(--bg-transparent) 8px,
            var(--bg-transparent) 100%
        ),
        -webkit-linear-gradient(
            left,
            var(--border-color) 0%,
            var(--border-color) 1px,
            var(--bg-transparent) 1px,
            var(--bg-transparent) 100%
        ),
        -webkit-linear-gradient(
            top,
            var(--border-color) 0%,
            var(--border-color) 1px,
            var(--bg-transparent) 1px,
            var(--bg-transparent) 100%
        ),
        -webkit-linear-gradient(
            top,
            var(--border-color) 0%,
            var(--border-color) 1px,
            var(--bg-transparent) 1px,
            var(--bg-transparent) 100%
        ),
        var(--bg-color);
    background: linear-gradient(
            to bottom,
            var(--bg-transparent) 0%,
            var(--bg-transparent) 1px,
            var(--bg-color) 1px,
            var(--bg-color) 8px,
            var(--bg-transparent) 8px,
            var(--bg-transparent) 100%
        ),
        linear-gradient(
            to bottom,
            var(--bg-transparent) 0%,
            var(--bg-transparent) 1px,
            var(--bg-color) 1px,
            var(--bg-color) 8px,
            var(--bg-transparent) 8px,
            var(--bg-transparent) 100%
        ),
        linear-gradient(
            to right,
            var(--border-color) 0%,
            var(--border-color) 1px,
            var(--bg-transparent) 1px,
            var(--bg-transparent) 100%
        ),
        linear-gradient(
            to bottom,
            var(--border-color) 0%,
            var(--border-color) 1px,
            var(--bg-transparent) 1px,
            var(--bg-transparent) 100%
        ),
        linear-gradient(
            to bottom,
            var(--border-color) 0%,
            var(--border-color) 1px,
            var(--bg-transparent) 1px,
            var(--bg-transparent) 100%
        ),
        var(--bg-color);
    background-size: 24px 32px;
    margin: 0 auto;
    background-position: 0 -1px, 0 0, -1px 0, 0 8px, 0px -1px;
}
 
.manuscript > p {
    display: flex;
    flex-wrap: wrap;
    margin: -8px -1px 8px 0;
    font-size: 16px;
    line-height: 24px;
}
 
.manuscript > p > span {
    display: inline-block;
    text-align: center;
    width: 24px;
    height: 24px;
    flex-shrink: 0;
    margin-top: 8px;
}
 
.manuscript > :empty:not(img) {
    height: 32px;
    margin-bottom: 8px;
}
 
.manuscript > :last-child {
    margin-bottom: 0;
}
 
.manuscript > img {
    display: block;
    width: auto;
    max-width: 100%;
    margin: 0 auto;
    object-fit: cover;
}
 
.manuscript > *:not(:last-child) {
    margin-bottom: 40px;
}

그래디언트도 조금 골이 아파지기 시작합니다.

원고 한 칸의 크기를 24px * 24px, 줄 간격을 8px로 잡고 만들었으니, 수정하실 때 참고해주세요.

JS

function initManuscript() {
    const manuscript = document.querySelectorAll(".manuscript");
    const handleResize = () => {
        manuscript.forEach((elt) => {
            resizeMnuascriptContainer(elt);
            resizeImage(elt);
        });
    };
 
    window.addEventListener("load", handleResize, { passive: true });
    window.addEventListener("resize", handleResize, { passive: true });
 
    manuscript.forEach((element) => {
        element.querySelectorAll("p").forEach((element) => {
            const text = element.innerText;
 
            element.innerHTML = "";
            [...text].forEach((word) => {
                const span = document.createElement("span");
                const textNode = document.createTextNode(word);
 
                span.appendChild(textNode);
                element.append(span);
            });
        });
    });
 
    handleResize();
}
 
function resizeMnuascriptContainer(element) {
    element.style.width = `${
        (Math.floor(element.parentElement.offsetWidth / 24) - 1) * 24 - 1
    }px`;
}
 
function resizeImage(element) {
    element.querySelectorAll("img").forEach((img) => {
        const { naturalWidth, naturalHeight } = img;
        const ratio = naturalHeight / naturalWidth;
        const newHeight = element.offsetWidth * ratio;
 
        img.height = Math.floor(newHeight - (newHeight % 32) - 8);
    });
}
 
initManuscript();

노트와 마찬가지로 이미지의 크기를 유동적으로 변경해주는 스크립트에 최초에 글자를 하나하나 뜯어서 span에 넣어주는 스크립트와, 원고지의 크기를 유동적으로 변경해주는 스크립트가 추가되었습니다.

원고 칸의 크기를 수정하시면 resizeManuscriptContainer, resizeImage에 들어있는 8의 배수(8, 24, 32)들을 모조리 수정해주시면 됩니다.

결과

여담

아무래도 노트와 달리 한 칸에 맞춰서 글자를 써야 하다 보니 글자를 모조리 span에 담아야 깔끔하게 작업할 수 있었습니다.
이 때문에 검색 엔진이 본문의 내용을 파악하기 힘들어질 수 있고, SEO에 악영향을 미칠 수 있습니다.
해당 스크립트가 SEO에 미치는 영향 등은 조사해보지 않았으니 이 점 유의해주세요.

Report an issue