js 로 간단 별점 정보
js 로 간단 별점본문
사용 샘플
!-->
class StarRating {
constructor(selector, options = {}) {
this.container = typeof selector === 'string' ? document.querySelector(selector) : selector;
if (!this.container) {
console.log("Invalid selector");
return false;
}
this.options = {
mode: 'display', // 'display' = 출력 전용, 'input' = 사용자 입력 가능
rating: 0, // 초기 별점 값 (소수점 포함 가능, 예: 4.5)
maxStars: 5, // 전체 별(또는 아이콘) 개수
iconType: 'star', // 아이콘 종류: 'star', 'heart', 'custom'
customIcon: '', // iconType이 'custom'일 경우 사용할 SVG path 문자열
starSize: '24px', // 각 별의 크기 (px, em, %, vw 등 유연하게 사용 가능)
starColor: 'gold', // 선택된 별의 색상
starEmptyColor: '#ccc', // 선택되지 않은 별(배경)의 색상
starGap: '4px', // 별 사이 간격 (CSS gap 값)
inputName: null, // 입력 모드에서 hidden input의 name 속성 값
showScore: false, // 별점 오른쪽에 점수 텍스트 출력 여부 (예: "7.3점")
liveHoverScore: false, // 마우스를 올릴 때 실시간 점수 텍스트 갱신 여부
scoreTextSize: '0.9em', // 점수 텍스트 폰트 크기
scoreTextColor: '#333', // 점수 텍스트 색상
scoreTextMargin: '8px', // 별과 점수 텍스트 사이 여백 (left margin)
onRate: null, // 입력 모드에서 사용자가 점수 클릭 시 실행할 콜백 함수
...options // 외부에서 전달된 사용자 정의 옵션 병합
};
this.currentRating = this.options.rating;
this.hoverRating = null;
this.injectStyles();
this.buildLayout();
}
injectStyles() {
if (document.getElementById('star-rating-style')) return;
const style = document.createElement('style');
style.id = 'star-rating-style';
style.textContent = `
.star-rating {
display: inline-flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
cursor: pointer;
}
.star-rating.readonly {
cursor: default;
}
.star-rating svg {
display: block;
flex-shrink: 0;
}
`;
document.head.appendChild(style);
}
buildLayout() {
this.container.innerHTML = '';
this.container.classList.add('star-rating');
if (this.options.mode === 'display') this.container.classList.add('readonly');
this.container.style.gap = this.options.starGap;
if (this.options.inputName && this.options.mode === 'input') {
this.hiddenInput = document.createElement('input');
this.hiddenInput.type = 'hidden';
this.hiddenInput.name = this.options.inputName;
this.hiddenInput.value = this.currentRating;
this.container.insertAdjacentElement('afterend', this.hiddenInput);
}
if (this.options.showScore) {
this.scoreText = document.createElement('span');
this.scoreText.className = 'star-rating-score';
this.scoreText.style.fontSize = this.options.scoreTextSize;
this.scoreText.style.color = this.options.scoreTextColor;
this.scoreText.style.marginLeft = this.options.scoreTextMargin;
this.container.appendChild(this.scoreText);
}
this.starElems = [];
for (let i = 1; i <= this.options.maxStars; i++) {
const star = this.createIcon(i);
if (this.options.mode === 'input') {
if (this.options.liveHoverScore) {
star.addEventListener('mousemove', (e) => this.onHoverMove(e, i));
} else {
star.addEventListener('mouseenter', () => this.onHover(i));
}
star.addEventListener('mouseleave', () => this.onHover(null));
star.addEventListener('click', (e) => this.onClick(e, i));
}
this.container.insertBefore(star, this.scoreText || null);
this.starElems.push(star);
}
this.updateDisplay();
}
createIcon(index) {
const icons = {
star: "M12 17.27L18.18 21 16.54 13.97 22 9.24 \ 14.81 8.63 12 2 9.19 8.63 2 9.24 7.45 13.97 5.82 21z",
heart: "M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 \ 2 6 4 4 6.5 4 8.28 4 10 5.4 12 7.5 \ 14 5.4 15.72 4 17.5 4 20 4 22 6 22 8.5 \ 22 12.28 18.6 15.36 13.45 20.04z",
circle: "M12 2a10 10 0 1 0 0 20 10 10 0 1 0 0-20z",
square: "M4 4h16v16H4z",
diamond: "M12 2l10 10-10 10-10-10z",
thumb: "M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57 \ .03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 \ 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05 \ .1-.5v-1.28z",
smile: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 \ 10-4.48 10-10S17.52 2 12 2zm0 15c-2.33 0-4.31-1.46-5.11-3.5h10.22 \ C16.31 15.54 14.33 17 12 17zm-4-7c-.83 0-1.5-.67-1.5-1.5S7.17 7 \ 8 7s1.5.67 1.5 1.5S8.83 10 8 10zm8 0c-.83 0-1.5-.67-1.5-1.5S15.17 7 \ 16 7s1.5.67 1.5 1.5S16.83 10 16 10z"
};
const pathD = this.options.iconType === 'custom'
? this.options.customIcon
: icons[this.options.iconType] || icons.star;
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("width", this.options.starSize);
svg.setAttribute("height", this.options.starSize);
svg.setAttribute("viewBox", "0 0 24 24");
svg.dataset.index = index;
const bg = document.createElementNS("http://www.w3.org/2000/svg", "path");
bg.setAttribute("d", pathD);
bg.setAttribute("fill", this.options.starEmptyColor);
svg.appendChild(bg);
const fg = document.createElementNS("http://www.w3.org/2000/svg", "path");
fg.setAttribute("d", pathD);
fg.setAttribute("fill", this.options.starColor);
fg.classList.add("star-fill");
svg.appendChild(fg);
return svg;
}
updateDisplay() {
const rating = this.hoverRating ?? this.currentRating;
this.starElems.forEach((svg, i) => {
const index = i + 1;
const fill = Math.min(Math.max(rating - index + 1, 0), 1);
const fillElem = svg.querySelector('.star-fill');
fillElem.setAttribute('clip-path', `inset(0 ${(1 - fill) * 100}% 0 0)`);
});
if (this.hiddenInput) {
this.hiddenInput.value = this.currentRating;
}
if (this.options.showScore && this.scoreText) {
this.scoreText.textContent = `${(this.hoverRating ?? this.currentRating).toFixed(1)}점`;
}
}
onHover(index) {
this.hoverRating = index;
this.updateDisplay();
}
onHoverMove(e, index) {
const rect = e.currentTarget.getBoundingClientRect();
const x = e.clientX - rect.left;
const percent = x / rect.width;
const value = index - 1 + percent;
this.hoverRating = Math.round(value * 10) / 10;
this.updateDisplay();
}
onClick(e, index) {
const rect = e.currentTarget.getBoundingClientRect();
const x = e.clientX - rect.left;
const percent = x / rect.width;
const value = index - 1 + percent;
this.currentRating = Math.round(value * 10) / 10;
this.hoverRating = null;
this.updateDisplay();
this.options.onRate?.(this.currentRating);
}
getRating() {
return this.currentRating;
}
setRating(val) {
this.currentRating = val;
this.updateDisplay();
if (this.hiddenInput) this.hiddenInput.value = val;
}
}
추천
6
6
댓글 4개
댓글에 사용하고 싶군요^^
추천하고 갑니다

감사합니다.
추천합니다.

감사합니다