방금 제가 드린 방식은 일종의 ‘위장 취업’ 같은 기술입니다. 에디터가 <script>라는 단어만 보면 미친 듯이 삭제하거나 글자를 바꿔버리니까(예: s-cript), 아예 그 단어를 안 쓰고 코드를 실행시키는 꼼수죠.
하나씩 설명해 드릴게요.
1. SVG (그림 파일) 속에 숨기기
원래 <svg>는 벡터 그래픽 이미지를 만드는 태그입니다. 에디터는 이걸 보고 “아, 이건 그냥 그림이구나” 하고 통과시켜 버립니다. 하지만 SVG 내부에는 애니메이션을 담당하는 태그들이 있는데, 얘네는 자바스크립트를 실행할 수 있는 능력이 있습니다.
2. 애니메이션 시작할 때(onbegin) 실행
<animate> 태그의 onbegin 속성을 사용했습니다.
- 원리: “그림의 투명도를 0에서 1로 바꿔라!”라는 아주 짧은 애니메이션을 설정합니다.
- 트리거: 애니메이션이 시작되는 순간(onbegin), 우리가 숨겨둔 자바스크립트 코드가 몰래 실행됩니다.
3. 왜 에디터가 못 지우나요?
대부분의 블로그 에디터(티스토리 등)는 보안 필터링을 할 때 보통 이런 식입니다.
- script 태그가 있는가? → 삭제
- onclick 같은 이벤트 속성이 있는가? → 삭제
- 하지만 <animate> 태그 안에 onbegin이 있는가? → 에디터: “이건 뭐야? 그림 옵션인가? 일단 둬봐.”
이런 맹점을 파고든 겁니다.
4. 코드의 작동 순서
- 사용자가 글을 클릭해서 들어옵니다.
- 눈에 보이지 않는 크기(width=”0″)의 그림이 로드됩니다.
- 애니메이션이 시작되자마자 자바스크립트 엔진이 가동됩니다.
- 지정한 카테고리 주소로 가서 최신글 5개를 긁어옵니다(fetch).
- 미리 만들어둔 div 구역에 예쁘게 리스트를 뿌려줍니다.
한 줄 요약:
**”에디터가 싫어하는 script 태그 대신, 그림 파일(svg)인 척 속여서 코드를 몰래 실행하는 기술”**입니다.
이 방식은 특히 티스토리처럼 본문 내 스크립트 검열이 심한 곳에서 최신글 리스트나 광고, 특수 기능을 넣을 때 아주 유용하게 쓰입니다. 이제 에디터 걱정 없이 쓰실 수 있을 거예요!
<div id="economy-posts-container"></div>
<div id="seo-tps"></div>
<svg width="0" height="0" style="position:absolute;">
<rect width="0" height="0">
<animate attributeName="opacity" from="0" to="1" dur="1s" onbegin="
(async function() {
const render = async (id, url) => {
const el = document.getElementById(id);
if(!el) return;
try {
const r = await fetch(url);
const t = await r.text();
const d = new DOMParser().parseFromString(t, 'text/html');
const items = Array.from(d.querySelectorAll('.post-item, .list_content li, .list-row')).slice(0, 5);
let h = '';
items.forEach(i => {
const l = i.querySelector('a')?.href || '#';
const title = i.querySelector('.title, .link_post, dt')?.textContent.trim() || '제목없음';
const thumb = i.querySelector('img')?.src || 'https://t1.daumcdn.net/tistory_admin/static/top/pc/img_common_tistory_empty.png';
h += `<a href='$l}' style='display:flex; gap:12px; margin-bottom:15px; text-decoration:none !important; color:inherit !important; align-items:center;'>
<img src='$thumb}' style='width:80px; height:60px; object-fit:cover; border-radius:6px; flex-shrink:0;'>
<div style='flex:1; overflow:hidden;'>
<h4 style='margin:0; font-size:15px; white-space:nowrap; text-overflow:ellipsis; overflow:hidden;'>$title}</h4>
</div>
</a>`;
});
el.innerHTML = h;
} catch(e) {}
};
await render('economy-posts-container', '/category/money-insight/daily-info');
await render('seo-tps', '/category/SEO/seo-tips');
})()
" />
</rect>
</svg>
