// DOM 요소 가져오기
const cssInput = document.getElementById(‘cssInput’);
const output = document.getElementById(‘output’);
const formatBtn = document.getElementById(‘formatBtn’);
const copyBtn = document.getElementById(‘copyBtn’);
const downloadBtn = document.getElementById(‘downloadBtn’);
const fileInput = document.getElementById(‘fileInput’);
const toast = document.getElementById(‘toast’);
// 모든 옵션 요소 가져오기
const options = {
sortProperties: document.getElementById(‘sortProperties’),
removeComments: document.getElementById(‘removeComments’),
alignColons: document.getElementById(‘alignColons’),
compressOutput: document.getElementById(‘compressOutput’),
vendorPrefix: document.getElementById(‘vendorPrefix’),
removeEmptyRules: document.getElementById(‘removeEmptyRules’),
convertRGBToHex: document.getElementById(‘convertRGBToHex’),
removeLastSemicolon: document.getElementById(‘removeLastSemicolon’),
indentSize: document.getElementById(‘indentSize’)
};
// 토스트 알림을 표시하는 함수
function showToast(message, isError = false) {
toast.textContent = message;
toast.classList.toggle(‘error’, isError);
toast.classList.add(‘show’);
setTimeout(() => toast.classList.remove(‘show’), 3000);
}
// 파티클 효과를 생성하는 함수
function createParticles(x, y) {
const particleCount = 15;
for (let i = 0; i < particleCount; i++) {
const particle = document.createElement('div');
particle.classList.add('particle');
const size = Math.random() * 8 + 4;
particle.style.width = `${size}px`;
particle.style.height = `${size}px`;
particle.style.left = `${x}px`;
particle.style.top = `${y}px`;
const angle = Math.random() * Math.PI * 2;
const distance = Math.random() * 100;
document.body.appendChild(particle);
const animation = particle.animate(
[
{ transform: 'translate(0, 0)', opacity: 1 },
{ transform: `translate(${Math.cos(angle) * distance}px, ${Math.sin(angle) * distance}px)`, opacity: 0 }
],
{ duration: 800, easing: 'ease-out' }
);
animation.onfinish = () => {
particle.remove();
};
}
}
// RGB를 HEX 색상으로 변환하는 함수
function rgbToHex(rgb) {
const match = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
if (!match) return rgb;
const r = parseInt(match[1]);
const g = parseInt(match[2]);
const b = parseInt(match[3]);
const toHex = (n) => {
const hex = n.toString(16);
return hex.length === 1 ? ‘0’ + hex : hex;
};
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
}
// CSS를 포맷하는 함수
function formatCSS(css) {
// 옵션이 체크되어 있을 경우 주석 제거
if (options.removeComments.checked) {
css = css.replace(/\/\*[\s\S]*?\*\//g, ”);
}
// 규칙으로 분할
let rules = css.split(‘}’)
.filter(rule => rule.trim())
.map(rule => {
let [selector, properties] = rule.split(‘{‘);
if (!properties) return ”;
// 선택자 정리
selector = selector.trim();
// 옵션이 체크되어 있을 경우 빈 규칙 건너뛰기
if (options.removeEmptyRules.checked && !properties.trim()) {
return ”;
}
// 속성을 배열로 분할
let propsArray = properties
.split(‘;’)
.filter(prop => prop.trim())
.map(prop => prop.trim());
// 벤더 접두사 정렬
if (options.vendorPrefix.checked) {
propsArray.sort((a, b) => {
const aPrefix = a.match(/^-\w+-/) || [”];
const bPrefix = b.match(/^-\w+-/) || [”];
return aPrefix[0].localeCompare(bPrefix[0]);
});
}
// 옵션이 체크되어 있을 경우 속성 정렬
if (options.sortProperties.checked) {
propsArray.sort((a, b) => {
const aProp = a.split(‘:’)[0].trim();
const bProp = b.split(‘:’)[0].trim();
return aProp.localeCompare(bProp);
});
}
// 옵션이 체크되어 있을 경우 RGB를 HEX로 변환
if (options.convertRGBToHex.checked) {
propsArray = propsArray.map(prop => {
const [name, value] = prop.split(‘:’);
if (value && value.includes(‘rgb(‘)) {
return `${name}: ${rgbToHex(value.trim())}`;
}
return prop;
});
}
// 콜론 정렬을 위한 속성 이름의 최대 길이 찾기
let maxPropLength = 0;
if (options.alignColons.checked) {
propsArray.forEach(prop => {
const propName = prop.split(‘:’)[0];
maxPropLength = Math.max(maxPropLength, propName.trim().length);
});
}
const indent = ‘ ‘.repeat(parseInt(options.indentSize.value));
// 속성 포맷팅
let formattedProps = propsArray.map((prop, index) => {
let [propName, propValue] = prop.split(‘:’);
propName = propName.trim();
propValue = propValue ? propValue.trim() : ”;
if (options.alignColons.checked) {
const padding = ‘ ‘.repeat(maxPropLength – propName.length);
return `${indent}${propName}${padding}: ${propValue}`;
} else {
return `${indent}${propName}: ${propValue}`;
}
});
// 옵션이 체크되어 있을 경우 마지막 세미콜론 제거
if (options.removeLastSemicolon.checked && formattedProps.length > 0) {
formattedProps[formattedProps.length – 1] = formattedProps[formattedProps.length – 1].replace(‘;’, ”);
}
// 속성 결합
let result = formattedProps.join(‘;\n’);
if (!options.removeLastSemicolon.checked) {
result += ‘;’;
}
// 선택자와 속성 결합
if (options.compressOutput.checked) {
return `${selector}{${formattedProps.join(‘;’)}}`;
} else {
return `${selector} {\n${result}\n}`;
}
})
.filter(rule => rule); // 빈 규칙 제거
return options.compressOutput.checked ?
rules.join(”) :
rules.join(‘\n\n’);
}
// 파일 가져오기 핸들러
fileInput.addEventListener(‘change’, (e) => {
const file = e.target.files[0];
if (file && file.type === ‘text/css’) {
const reader = new FileReader();
reader.onload = (event) => {
cssInput.value = event.target.result;
showToast(‘CSS 파일을 성공적으로 가져왔습니다’);
};
reader.onerror = () => {
showToast(‘파일 읽기 오류가 발생했습니다’, true);
};
reader.readAsText(file);
} else {
showToast(‘유효한 CSS 파일을 선택해주세요’, true);
}
});
// 포맷 버튼 핸들러
formatBtn.addEventListener(‘click’, (e) => {
const css = cssInput.value.trim();
if (!css) {
showToast(‘CSS 코드를 입력해주세요’, true);
return;
}
try {
const formattedCSS = formatCSS(css);
output.value = formattedCSS;
copyBtn.disabled = !formattedCSS;
downloadBtn.disabled = !formattedCSS;
const rect = e.target.getBoundingClientRect();
createParticles(e.clientX, rect.top);
showToast(‘CSS가 성공적으로 포맷되었습니다’);
} catch (error) {
showToast(‘CSS 포맷 중 오류가 발생했습니다’, true);
console.error(error);
}
});
// 복사 버튼 핸들러
copyBtn.addEventListener(‘click’, async () => {
try {
await navigator.clipboard.writeText(output.value);
showToast(‘CSS가 클립보드에 복사되었습니다’);
const originalText = copyBtn.textContent;
copyBtn.textContent = “복사 완료!”;
setTimeout(() => {
copyBtn.textContent = originalText;
}, 2000);
} catch (error) {
showToast(‘클립보드에 복사하는 중 오류가 발생했습니다’, true);
}
});
// 다운로드 버튼 핸들러
downloadBtn.addEventListener(‘click’, () => {
try {
const blob = new Blob([output.value], { type: ‘text/css’ });
const url = URL.createObjectURL(blob);
const a = document.createElement(‘a’);
a.href = url;
a.download = ‘formatted.css’;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
showToast(‘CSS 파일이 다운로드되었습니다’);
} catch (error) {
showToast(‘파일 다운로드 중 오류가 발생했습니다’, true);
}
});
// 입력 변경 핸들러
cssInput.addEventListener(‘input’, () => {
copyBtn.disabled = true;
downloadBtn.disabled = true;
});
// 초기화
copyBtn.disabled = true;
downloadBtn.disabled = true;
CSS 포매터란 무엇인가요?
CSS 포매터는 적절한 들여쓰기, 간격, 줄 바꿈을 추가하여 CSS 코드를 정리하고 보기 좋게 만드는 도구입니다. CSS 파일의 가독성과 일관성을 향상시킵니다.
왜 CSS 포매터를 사용해야 하나요?
CSS 포매터를 사용하면 CSS 코드를 읽고 디버깅하기 쉬워지고, 팀이나 프로젝트 전체에서 일관된 형식을 유지할 수 있으며, 문법 오류나 중복 코드를 쉽게 발견하고, 특히 큰 CSS 파일의 유지 보수성을 높여줍니다.
CSS 포매터는 무료인가요?
네, 저희 CSS 포매터는 무료로 사용할 수 있습니다.
CSS 포매터는 어떻게 작동하나요?
CSS 포매터는 CSS 코드를 선택자, 속성, 값과 같은 구조적 요소로 파싱한 후 일관된 들여쓰기, 줄 바꿈, 간격으로 코드를 재구성하여 기능은 그대로 유지한 채 포맷된 CSS를 출력합니다.
포맷 규칙을 사용자 지정할 수 있나요?
네, CSS 압축 및 축소, 속성 정렬, 콜론 정렬, 주석 제거, 들여쓰기 스타일 등 다양한 포맷 옵션과 규칙을 선택할 수 있습니다.
CSS 포매터가 코드 기능에 영향을 주나요?
아니요, 저희 CSS 포매터는 코드의 외형만 변경하며 동작에는 영향을 주지 않습니다. CSS는 유효하고 정상적으로 작동합니다.
CSS 포매터가 큰 파일도 처리할 수 있나요?
네, 저희 CSS 포매터는 큰 파일도 처리할 수 있습니다. 다만 아주 큰 파일의 경우 성능은 브라우저와 시스템 리소스에 따라 달라질 수 있습니다.
포맷팅과 미니파잉의 차이는 무엇인가요?
포맷팅은 적절한 들여쓰기와 간격으로 코드를 조직하여 가독성을 높이는 반면, 미니파잉은 불필요한 문자(공백, 주석 등)를 제거해 파일 크기를 줄이지만 기능에는 영향을 주지 않습니다.