🎨 실시간 디자인 에디터 및 배경 연출 소개
본 문서는 재빌드 및 재배포 없이 테마 설정을 실시간으로 반영하는 시스템과 4가지 배경 설정(단색, 그라디언트, 배경 이미지, 커스텀 자바스크립트 캔버스)에 대한 기본 구성을 소개합니다.
🌌 1. 실시간 디자인 에디터 개요
디자인 에디터에서 설정값을 변경하고 저장할 때 블로그 서버나 CDN 서비스를 다시 빌드하고 배포할 필요가 전혀 없습니다.
저장 즉시 데이터베이스가 갱신되며, 방문자의 브라우저 화면에 CSS 변수와 배경 렌더링 모듈이 실시간으로 반영됩니다.
블로그에 최종적으로 반영하기 전에, 어드민 페이지 내의 프리뷰 화면을 통해 디자인을 미리 가늠해볼 수 있습니다.
[!WARNING]
⚠️ 프리뷰와 실제 블로그 디자인의 차이점 주의
디자인 에디터의 프리뷰 기능은 실제 방문자 화면과 100% 동일한 환경을 보장하지 않습니다. 브라우저의 CSS 변수 해석 방식이나 자바스크립트 캔버스 렌더링 조건 등에 따라 어드민 프리뷰와 실제 블로그 화면에 미세한 차이가 발생할 수 있습니다.
따라서 디자인 설정을 변경한 뒤에는 반드시 실제 블로그 메인 화면에 접속하여 새로고침(F5)을 통해 최종 연출 상태를 눈으로 검증하시는 것을 강력히 권장합니다.
🧱 레이아웃 및 테마 설정 항목
① 레이아웃 구성
- 블로그 기본 구조: 메인 페이지의 구조(예: 2컬럼, 3컬럼 등)를 선택할 수 있습니다.
- 컬럼 폭 비율: 메인 콘텐츠 영역과 각 사이드바 컬럼 간의 너비 비율(예: 1:2:1, 2:1:2 등)을 조절합니다.
- 화면 최대 가로 폭: 전체 웹사이트가 확장될 수 있는 최대 가로 해상도(예: 1200px, 1400px 등)를 제한합니다.
- 세부 레이아웃 수치: 페이지 좌우 여백(Side Margin), 콘텐츠 카드 모서리 둥글기(Border Radius), 깊이감을 더해주는 그림자(Box Shadow) 효과를 미세 조절합니다.
② 테마 기본 색상 구성
- 주 테마 색상(Primary), 보조 색상(Secondary), 본문 글자 색상(Text), 포인트 강조 색상(Accent), 카드 및 위젯 배경(Card Bg), 그리고 테두리 색상(Border)을 조율합니다.
③ 타이포그래피 (서체)
- Google Fonts 서체 목록 중에서 원하는 폰트 패밀리 명칭을 직접 기입하여 적용하고, 본문 폰트 종류와 기본 글자 크기(Base Font Size)를 지정할 수 있습니다.
④ 위젯 배치 및 순서 관리
- 배치하고 싶은 위젯들을 마우스 드래그 앤 드롭으로 각 컬럼에 자유롭게 배치하고 순서를 지정할 수 있습니다.
🧱 2. 기기별 독립 위젯 배치
레이아웃 설계 시 마우스 드래그 앤 드롭으로 위젯의 위치와 순서를 지정할 수 있으며, 접속하는 독자의 기기 크기에 맞춰 레이아웃을 다르게 구성할 수 있는 필터링 기능을 제공합니다.
- Desktop (데스크톱 전용): 화면이 넓은 PC 환경에서만 해당 위젯(예: 태그 클라우드, 카테고리 트리 등)을 출력합니다. 모바일 접속 시에는 HTML 단계에서 요소를 사전에 제외하여 로딩 속도를 유지해 줍니다.
- Mobile (모바일 전용): 데스크톱에서는 보이지 않고, 스마트폰 화면 크기에서만 모바일 친화적으로 단독 출력되도록 제한합니다.
🌈 3. 영역별 배경 설정 종류
블로그의 배경 화면은 헤더(Header), 메인(Main), 푸터(Footer) 영역으로 구분하여 각각 독립적으로 디자인을 연출할 수 있습니다.
- 상속(Inheritance) 기능: 헤더와 푸터의 경우, '상위 설정 상속'을 활성화하여 메인(Main) 영역의 배경을 그대로 이어받도록 간편하게 설정할 수도 있습니다.
- 4가지 배경 타입: 영역별로 적용 가능한 배경 화면은 총 4가지 타입을 지원합니다.
① Solid (단색 배경)
- 선호하는 색상의 헥스(HEX) 코드(예:
#3b82f6)나 HSL 색상 코드를 지정하여 가장 깔끔하고 포스트 본문에 집중하기 좋은 화면을 만듭니다.
② Gradient (그라디언트 배경)
- 그라디언트 빌더(Gradient Builder) 도구를 사용하여 여러 컬러가 자연스럽게 섞이는 선형 그라디언트를 구성합니다.
- 각도 조절(Direction) 슬라이더와 **색상 정지점(Stops)**을 마우스로 추가하고 이동하며 시작색, 중간 경유색, 종점색을 배합할 수 있습니다.
③ Image (배경 이미지 & 글래스모피즘 필터 조합)
- 배경으로 사용할 사진의 주소(URL)를 입력하거나, 업로드(Upload) 버튼을 사용해 미디어 파일을 보관함에 추가합니다.
- 업로드 이미지 최적화: 이미지를 등록하면 시스템이 WebP 형식으로 자동 변환하여 로딩 성능 저하를 방지합니다.
- 글래스모피즘 효과: 배경 이미지가 텍스트 가독성을 저해하지 않도록, 아래의 제어 설정을 활용해 반투명 유리창 스타일을 구현할 수 있습니다.
| 설정 항목 | 권장 조작 범위 | 상세 설명 |
|---|---|---|
| 배경 블러 (Glass Blur) | 5px ~ 15px |
글자가 놓이는 콘텐츠 카드 하단에 투명한 유리 재질 느낌의 필터를 입힙니다. |
| 유리창 틴트 농도 (Overlay Opacity) | 10% ~ 30% |
유리창 뒤편에 깔리는 반투명 마스크의 불투명도를 결정합니다. |
| 유리창 틴트 색상 (Overlay Color) | #000000 또는 #ffffff |
마스크 색상을 어둡게(Dark) 혹은 밝게(Light) 연출하여 글자의 선명한 대비(Contrast)를 확보합니다. |
④ Custom JavaScript (커스텀 자바스크립트 및 Canvas 배경)
- 방문자 브라우저에서 배경용 Canvas 애니메이션을 직접 구동할 수 있도록 커스텀 자바스크립트 입력을 지원합니다.


- 캔버스 요소(
canvas id="bg-canvas")에 직접 렌더링을 제어할 수 있으며, 이와 연동되는 세부 보안 및 절전 시스템은 아래 **[4. 커스텀 자바스크립트 연동 사양]**에서 설명합니다.
⚡ 4. 커스텀 자바스크립트 연동 사양
보안 사고를 예방하고 모바일 기기 배터리를 보존하기 위해 아래와 같은 격리 정책 및 절전 시스템이 적용됩니다. 외부 라이브러리나 프레임워크는 사용할 수 없으며, 오직 순수 바닐라 자바스크립트(Vanilla JS)로만 작성해야 합니다.
🔒 격리 보안 시스템 (Sandbox & CSP)
블로그 어드민일지라도 악성 스크립트 유입으로 인한 보안 사고를 막기 위해 다음과 같은 보안 정책이 상시 가동됩니다.
- 격리된 Sandbox 구조: 캔버스 코드는 오직 스크립트 실행만 인가된 격리된
iframe내부에서 구동됩니다. 부모 페이지의 DOM 노드나 로그인 정보 등에 접근하여 데이터를 탈취하거나 위변조하는 동작이 기술적으로 차단됩니다. - 콘텐츠 보안 정책 (CSP) 제한: 외부 서버와의 네트워크 통신 및 외부 스크립트 호출이 전면 차단됩니다. 따라서 외부 서버로 데이터를 유출하는 것이 불가능합니다.
- JS API 허용 차단: 보안을 해칠 수 있는
fetch,XMLHttpRequest,WebSocket,eval,new Function,document.cookie,localStorage등의 API가 감지되면 보안 필터(jsValidator.ts)가 이를 자동으로 주석 처리(/* f_e_t_c_h (blocked) */)하여 무력화합니다.
🔋 배터리 보존 및 절전 기술
- 화면 밖 자동 일시정지: 사용자가 화면을 스크롤하여 배경 애니메이션이 완전히 시야에서 사라지면, 브라우저가 CPU/GPU를 낭비하지 않도록 프레임 렌더링 루프를 일시정지(Sleep) 상태로 전환합니다. 화면에 다시 노출되면 즉각 재생(Resume)됩니다.
- 모바일 프레임 스로틀링: 스마트폰이나 태블릿 등 모바일 기기로 접속하는 경우 기기 발열 방지를 위해 기본적으로 애니메이션이 정지됩니다. "모바일 기기에서도 애니메이션 구동" 옵션을 명시적으로 체크하면 실행할 수 있으며, 이때 성능에 부담이 가지 않도록 파티클 개수를 자동으로 축소 제어하는 매개변수가 연동됩니다.
📝 5. 예제 스크립트 코드 3종
배경 설정 메뉴에서 **[Custom JavaScript]**를 선택하고, 아래 코드 중 하나를 복사하여 적용할 수 있습니다. 추후 몇 가지 예제 코드가 더 추가될 수 있습니다.
[!NOTE]
스크립트를 작성할 때에는 배경을 그릴 대상 캔버스 요소를 찾기 위해 반드시document.getElementById('bg-canvas')아이디명을 사용하여 컨텍스트를 획득해 주시기 바랍니다.
❄️ 예제 A. 겨울 눈송이 (Snowfall)
화면 위에서 눈송이들이 아래로 천천히 떨어지는 배경 애니메이션입니다.
(function() {
const canvas = document.getElementById('bg-canvas');
if (!canvas) return;
const ctx = canvas.getContext('2d');
let width = canvas.width = window.innerWidth;
let height = canvas.height = window.innerHeight;
// 모바일 스로틀 감지
const divisor = (window.bgConfig && window.bgConfig.mobileThrottleDivisor) || 1;
const maxSnowflakes = Math.floor(100 / divisor);
const snowflakes = [];
class Snowflake {
constructor() {
this.reset();
this.y = Math.random() * height; // 초기 임의 고도 설정
}
reset() {
this.x = Math.random() * width;
this.y = -10;
this.radius = Math.random() * 3 + 1;
this.speed = Math.random() * 1 + 0.5;
this.opacity = Math.random() * 0.6 + 0.2;
}
update() {
this.y += this.speed;
// 부드러운 흔들림 주기
this.x += Math.sin(this.y / 30) * 0.5;
if (this.y > height || this.x < 0 || this.x > width) {
this.reset();
}
}
draw() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.fillStyle = `rgba(25, 25, 255, ${this.opacity})`;
ctx.fill();
}
}
// 파티클 생성
for (let i = 0; i < maxSnowflakes; i++) {
snowflakes.push(new Snowflake());
}
function animate() {
ctx.clearRect(0, 0, width, height);
for (let i = 0; i < snowflakes.length; i++) {
snowflakes[i].update();
snowflakes[i].draw();
}
requestAnimationFrame(animate);
}
// 창 크기 조절 이벤트
window.addEventListener('resize', () => {
width = canvas.width = window.innerWidth;
height = canvas.height = window.innerHeight;
});
animate();
})();
🕸️ 예제 B. 별자리 연결망 (Constellation Network)
파티클 노드들이 무작위로 부유하며, 서로 가까워지면 얇은 선으로 연결되어 그물 형태를 구성하는 배경 애니메이션입니다.
(function() {
const canvas = document.getElementById('bg-canvas');
if (!canvas) return;
const ctx = canvas.getContext('2d');
let width = canvas.width = window.innerWidth;
let height = canvas.height = window.innerHeight;
const divisor = (window.bgConfig && window.bgConfig.mobileThrottleDivisor) || 1;
const particleCount = Math.floor(80 / divisor);
const particles = [];
const connectionDistance = 100;
class Particle {
constructor() {
this.x = Math.random() * width;
this.y = Math.random() * height;
this.vx = (Math.random() - 0.5) * 0.8;
this.vy = (Math.random() - 0.5) * 0.8;
this.radius = Math.random() * 2 + 1.5;
}
update() {
this.x += this.vx;
this.y += this.vy;
// 화면 경계 튕기기
if (this.x < 0 || this.x > width) this.vx *= -1;
if (this.y < 0 || this.y > height) this.vy *= -1;
}
draw() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.fillStyle = 'rgba(99, 102, 241, 0.4)'; // 파스텔 인디고 컬러
ctx.fill();
}
}
for (let i = 0; i < particleCount; i++) {
particles.push(new Particle());
}
function drawLines() {
for (let i = 0; i < particles.length; i++) {
for (let j = i + 1; j < particles.length; j++) {
const dx = particles[i].x - particles[j].x;
const dy = particles[i].y - particles[j].y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < connectionDistance) {
const alpha = (connectionDistance - dist) / connectionDistance * 0.18;
ctx.beginPath();
ctx.moveTo(particles[i].x, particles[i].y);
ctx.lineTo(particles[j].x, particles[j].y);
ctx.strokeStyle = `rgba(99, 102, 241, ${alpha})`;
ctx.lineWidth = 1;
ctx.stroke();
}
}
}
}
function animate() {
ctx.clearRect(0, 0, width, height);
for (let i = 0; i < particles.length; i++) {
particles[i].update();
particles[i].draw();
}
drawLines();
requestAnimationFrame(animate);
}
window.addEventListener('resize', () => {
width = canvas.width = window.innerWidth;
height = canvas.height = window.innerHeight;
});
animate();
})();
🌊 예제 C. 파도 일렁임 (Fluid Sine Waves)
화면 하단에서 여러 갈래의 반투명 파도가 부드럽게 일렁이는 배경 애니메이션입니다.
(function() {
const canvas = document.getElementById('bg-canvas');
if (!canvas) return;
const ctx = canvas.getContext('2d');
let width = canvas.width = window.innerWidth;
let height = canvas.height = window.innerHeight;
let wave1 = {
y: height * 0.85,
length: 0.005,
amplitude: 25,
frequency: 0.012
};
let wave2 = {
y: height * 0.88,
length: 0.008,
amplitude: 15,
frequency: 0.022
};
let increment = 0;
function animate() {
ctx.clearRect(0, 0, width, height);
// 첫 번째 뒷 파도 그리기 (반투명 파스텔 청록)
ctx.beginPath();
ctx.moveTo(0, height);
for (let i = 0; i < width; i++) {
ctx.lineTo(i, wave1.y + Math.sin(i * wave1.length + increment) * wave1.amplitude);
}
ctx.lineTo(width, height);
ctx.fillStyle = 'rgba(45, 212, 191, 0.1)';
ctx.fill();
// 두 번째 앞 파도 그리기 (반투명 파스텔 하늘)
ctx.beginPath();
ctx.moveTo(0, height);
for (let i = 0; i < width; i++) {
ctx.lineTo(i, wave2.y + Math.sin(i * wave2.length - increment * 1.5) * wave2.amplitude);
}
ctx.lineTo(width, height);
ctx.fillStyle = 'rgba(56, 189, 248, 0.15)';
ctx.fill();
// 모바일 환경에 따른 속도 차등 제어
const speedFactor = (window.bgConfig && window.bgConfig.mobileThrottleDivisor) ? 0.3 : 1;
increment += wave1.frequency * speedFactor;
requestAnimationFrame(animate);
}
window.addEventListener('resize', () => {
width = canvas.width = window.innerWidth;
height = canvas.height = window.innerHeight;
wave1.y = height * 0.85;
wave2.y = height * 0.88;
});
animate();
})();
📱 6. 모바일 개별 배경 설정
기기 환경에 맞춰 배경 설정을 차별화하여 성능을 최적화할 수 있습니다.
- 데스크톱 배경: 데스크톱 환경에서는 Custom JavaScript를 선택하여 원하는 Canvas 애니메이션을 구동할 수 있습니다.
- 모바일 개별 배경 체크: [모바일 기기에서 데스크톱과 다른 배경 설정 사용] 스위치를 활성화합니다.
- 모바일 배경 경량화: 독립된 모바일 설정에서 배경을 Solid(단색) 또는 **Gradient(그라디언트)**로 선택하여 모바일 리소스 부하를 최소화할 수 있습니다. (선택사항)
- 이원화 효과: 배터리 소모와 성능이 중요한 모바일 기기에는 스크립트 실행 부하가 없는 가벼운 배경을 제공하고, PC 환경에는 다채로운 시각 효과를 온전히 표현하도록 배경을 이원화할 수 있습니다.
💡 7. 영역별 배경 조합 및 상속 예시
헤더, 메인, 푸터에 다양한 배경 타입을 조합하여 독창적인 블로그 디자인을 연출할 수 있습니다.
- 예시 A (독립 연출)
- 설정: 헤더 = Solid(단색) / 메인 = Image(배경 이미지) / 푸터 = Custom JavaScript
- 결과: 각 영역이 지정된 개별 설정에 따라 독립적으로 나타납니다.
- 예시 B (상속 연출)
- 설정: 헤더 = 상위 설정 상속 / 메인 = Custom JavaScript / 푸터 = 상위 설정 상속
- 결과: 메인 배경으로 지정한 Custom JavaScript가 헤더와 푸터에 모두 상속되어 화면 전체에 통합된 캔버스 애니메이션이 렌더링됩니다.
댓글 0개
댓글을 작성하려면 로그인이 필요합니다.