js 로 간단 별점 > 그누보드5 팁자료실

그누보드5 팁자료실

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

댓글 4개

전체 2,741 |RSS
그누보드5 팁자료실 내용 검색

회원로그인

(주)에스아이알소프트 / 대표:홍석명 / (06211) 서울특별시 강남구 역삼동 707-34 한신인터밸리24 서관 1402호 / E-Mail: admin@sir.kr
사업자등록번호: 217-81-36347 / 통신판매업신고번호:2014-서울강남-02098호 / 개인정보보호책임자:김민섭(minsup@sir.kr)
© SIRSOFT