Пять звёздочек

Продолжая тему небольших полезных CSS-решений, предлагаю вашему вниманию интересную реализацию механизма рейтингов. С виду — всё просто: пять звёздочек, клик по каждой фиксирует рейтинг на позиции от одного до пяти баллов. Если же какой-то рейтинг уже имеется, при наведении он должен сбрасываться, чтобы пользователь имел возможность выразить своё мнение.

Каких только решений не придумано для этой тривиальной задачи — от сомнительного списка элементов <img> до более-менее внятных решений с помощью фоновых изображений. Но одно неизменно — всё это сдобрено хорошей порцией JS-кода, отвечающего за необходимое поведение. Что вполне логично приводит к тому, что без JS, в лучшем случае, не видно всей красоты, а в худшем — вообще нельзя проголосовать. В общем, не слишком хорошо.

Поэтому я предлагаю новое решение: пять звёздочек и его особенности:

Самая интересная часть CSS-кода:

.voting A.cur,
.voting A:hover,
.voting:hover A.cur:hover,
.voting:hover A:hover {
    background:#FC0 url(../i/stars.png) no-repeat;
    }
.voting:hover A.cur {
    background:none;
    }

Признаюсь честно, вещей вроде E:hover E:hover { … } мне до сих пор писать не приходилось.

Также хотелось бы упомянуть про интересное JS-решение для определения версии IE, чтобы для шестой его версии и младше эмулировать псевдо-класс :hover для списков. И вот каким образом я получаю переменную ltIE7 (less than IE7):

var ltIE7 = false
/*@cc_on
    @if (@_jscript_version < 5.7)
        ltIE7 = true
    @end
@*/

Эта пляска с собачками называется «условное выполнение» (Conditional Compilation) и формально вообще не является JavaScript'ом, это JScript. Если я не ошибаюсь, только его IE и понимает, а официальный JavaScript работает только благодаря довольно широкой совместимости этих языков. Что-то до боли знакомое, вы не находите? Прямо условные комментарии (Conditional Comments), только в JS-реинкарнации. Кто знает, если копнуть MSDN поглубже, может быть найдётся что-то похожее и для CSS?

По аналогии с предыдущим примером, можно получить и переменную ltIE6 (less than IE6), вычислив версию JScript, использующуюся в браузере. Для IE6 она равна 5.5:

@if (@_jscript_version < 5.6)
    ltIE6 = true
@end

Более подробное описание этой чудо-технологии можно найти в статье «Conditional Compilation Variables». Пятизвёздочных вам рейтингов ;)

Комментарии

44

Хм, интересно. Когда нужно было сделать рейтинг звездочками, я долго искал вариант. Перепробовал по-моему штуки 3-4. У меня все время были проблемы с IE, то звездочки не хотели рисоваться при hover (хотя там был использован какой-то хак), то при результате они не закрашивались. Вобщем намучался :)

Вопрос: как явно при загрузке указать сколько голосов уже отдано? Что-то парамтер не нашел такой.

Да, наверное всё-таки стоит добавить в пример предустановленный класс. А вообще: <a href="#" class="cur">

Жаль ещё что нет возможности выставить в процентах рейтинг. В моем варианте кажется было использована ширина закраски расчитанная в скрипте.

Забыл добавить спасибо :) В новом проекте попробую использовать ;)

Мне затея с заполненными наполовину звёздочками совсем не нравится — отличить на глаз 2,3 балла от 2,7 баллов я не смогу. Да и голосовать за такое мышкой будет нереально.

Нет нет, голосовать можно только от 1 до 5 и только за один пункт. Просто при выводе будет процент всей суммы рейтинга к кол-ву голосов

Если говорить о семантике, то, все-таки, необходимо использовать input type="radio"

Если говорить о семантике…

Вообще, я согласен… Голосование это скорее отправка формы с одним из выбранных значений, чем переход на другую страницу с параметром. Стоит подумать как это дело трансформировать в звёздочки, попутно спрятав кнопку «Голосовать»…

Олег, я в другом сомневался — что он понимает, JScript, совместимый с JS или непосредственно JavaScript…

pepelsbey, спасибо, может и пригодится.
А можно, одно не принципиальное улучшение? :)

Мне кажется, что строку:
this.className = this.className.replace( phover,'');

Было бы лучше записать так:
this.className = this.className.replace(/(^| )phover($| )/,'');
Т.е. регулярным выражением проверять, что phover — это отдельное слово, а не часть другого слова (вдруг, завтра появится у того же элемента еще и класс, к примеру: "phovering").

А вообще, пять звёздочек — интересная вещь и реализовано хорошо.

Выглядит красиво, только вот откуда взято свойство xdisplay? Впервые вижу такое:

    .voting SPAN {
        xdisplay:none;
        }

Упс, это я дебажил так — это всё равно, что закомментировать, только невалидно.
Удалю, спасибо )

Кто знает, если копнуть MSDN поглубже, может быть найдётся что-то похожее и для CSS?
В блоге IE вроде писали, что нет и не будет.

Про JS у Dean'а Edwards'а в блоге развлекались.

Если говорить о семантике, то, все-таки, необходимо использовать input type="radio"

Упаси Боже! Это один из тех примеров, когда семантика конфликтует с usability. Элементы форм должны выглядеть как элементы форм, и именно поэтому браузеры не дадут кардинально изменить внешний вид input type="radio". В противном случае дизайнеры бы уже давно извратились над всеми элементами форм :-)

Голосование это скорее отправка формы с одним из выбранных значений, чем переход на другую страницу с параметром.

...а отправка формы - это технически тот же переход на другую страницу с параметром.

В любом случае, если извращаться с формой, то потребуется куда больше JavaScript. Ведь input type="radio" будет прорисовываться поверх любого бэкграунда, следовательно, так или иначе его придется убрать (либо скрыть, либо что-то спозиционировать на него сверху). Если радиобатон убрать, он не сможет получить фокус при клике (хотя если поместить input в label, то может и прокатит, я не пробовал). Но на мой взгляд, заморочек и потенциальных проблем здесь больше, чем преимуществ :-)

Владимир, мне кажется, что используя подобную методику, можно вообще полсайта сверстать на таблицах с атрибутами и спрятать внутрь условных комментариев — чтобы в IE всё было красиво, а вторую половину нормально, со всякими CSS3-трюками.

В общем, красота красотой, а я не готов: а) мусорить в коде лишними условными комментариями б) вкладывать блочные элементы в строчные, даже внутри условных комментариев.

Вопрос спорный... В данном случае условный комментарий нужен только для того, чтобы не использовать JavaScript для IE6 - никаких других целей (в том числе презентационных) он не несет. В принципе, я считаю, что те два комментария были оправданы, ибо в ТЗ стояло требование, чтобы все работало как с JS, так и без. А IE6 всё еще очень много народа пользуется.

можно вообще полсайта сверстать на таблицах с атрибутами и спрятать внутрь условных комментариев

Возможно, я не пытался использовать такие хаки для презентационных возможностей. Но если так сделать, будет неудобство: нужно будет поддерживать две версии кода.

а) мусорить в коде лишними условными комментариями

Тем не менее, те же условные комментарии, но под другим соусом появляются в JavaScript, а также условные комментарии используются для скармливания своего CSS ишакам меньше 8 версии :-)

б) вкладывать блочные элементы в строчные, даже внутри условных комментариев

На мой взгляд, это то же самое, чито использовать zoom: 1 для hasLayout :-)

В любом случае, условный комментарий можно убрать и повесить whatever:hover.

скармливания своего CSS ишакам меньше 8 версии

сомневаюсь, что для 8ой версии не придется писать отдельные стили :D

Скажу проще, использовать громоздкие условные комментарии с таблицей для каждой группы звёздочек — это безрассудно с т.з. массивности кода и беспринципно с т.з. светлой идеи правильной вёрстки.

Я всё-таки стараюсь трюковать в отдельных файлах, а документ держать логически правильным и чистым.

…а если можно убрать и повесить whatever:hover, то смысл?

а если можно убрать и повесить whatever:hover, то смысл?

Смысл в том, что whatever:hover - это JScript (htc-файл). Отключение JS отключает всякие htc.

это безрассудно с т.з. массивности кода и беспринципно с т.з. светлой идеи правильной вёрстки.

Возможно... А с точки зрения accessibility? Как программист, занимающийся разработкой, хм, весьма своеобразных :-) сайтов, я обратил внимание, что не все рискуют включать JavaScript. Кто-то по соображениям безопасности (пользователи IE6, привет!), кто-то из-за того, что анонимайзер скрипты режет, кто-то с мобилок трафик экономит... не суть. На мой взгляд, пара десятков байт (которые можно очень сократить) особой роли не играет (тем более, если используется gzip), но в то же время позволяет таким пользователям нормально использовать сайт. Лично для меня accessibility в данном случае перевешивает.

Я, например, против использования всяких expression в CSS: на мой взгляд, это не comme il faut, особенно, когда дизайн начинает расползаться при выключенном JS.

Я всё-таки стараюсь трюковать в отдельных файлах, а документ держать логически правильным и чистым.

С другой стороны, Vary: user-agent и Content Negotiation тоже никто не отменял, хотя здесь во мне говорит программист.

PS - про чистоту условно согласен, про логическую правильность - нет: комментарии на логику не влияют :-)

что для 8ой версии не придется писать отдельные стили

В IE8 beta1 приходится - с отрицательными границами там не всё гладко. А также из-за распознавания стилей, предназначенных для IE/Mac (это о вреде хаков).

Вадик, хочу тебе сразу сказать, что делать рейтингование ссылкой — очень плохая идея. Дело в том, что есть очень хорошее правило: GET запрос не должен ничего модифицировать. Это не просто прихоть, а реальная головная боль, которую создает, например, Google Page Accelerator, который просто подтаскивает все урлы на странице.

Т.е. если хочется делать полный fallback механизм, то надо делать форму с map-ом.

вставлю свои пять копеек. Вообще, input'ы нужно. Зашел к ваш человек с отключенныим картинками -- что он увидел? ничего... Этот случай-то фиксится через какой-то текст в ссылках. А если еще и с отключенными стилями и скрипты сломались?

Как вариант -- рисовать звездочки поверх соответствующих input'ов, перекрывая их через normal flow (чтобы с zIndex возни было поменьше, но это уже на любителя).

Просто на западе достаточно много мусолят тему WCAG / WAI / ARIA, у нас не то, что забивают, обычно об этом просто даже не подозревают.

P.S. при загрузке
http://blog.sjinks.org.ua/css/173-five-stars-without-javascript/
у меня в первый раз не подключились стили -- там с хостингом все ок?

Интересно, как заставить эти звездочки не быть активными (то есть, когда юзер уже проголосовал, надо высвечивать строго текущий рейтинг и не мигать звездами при наведении мышкой).

Пробовал оставить только cur-элемент, но почему-то при наведении мышью на ul.voter правее, чем правая граница cur, весь ul.voter закрашивается серыми звездами, хотя стиля для hover тут никакого нет...

Чтобы сделать рейтинг неактивным, нужно после клика прописать JS'ом для UL какой-нибудь класс, вроде inactive, а в CSS описать, что с таким классом UL ведёт себя как бревно и ни на что не реагирует.

Уж простите, это не ultimate-решение, а лишь интересная методика, которую можно развить под себя.

Примерно так.

Решение оказалось несложным. Если нужно сделать так, чтобы рейтинг оставался фиксированным - надо:
1. выводить в HTML только элемент с классом cur;
2. назначить ul дополнительный класс inactive;
3. дописать в CSS после (именно после!) стиля

.voting:hover a.cur,
.voting.phover a.cur {
    background:none
}

этот стиль:

.inactive:hover a.cur{
    background:#FC0 url('../../images/msk/images_ru/stars.png') no-repeat
}

Если будут баги - пишите )

Спасибо.
Я наверное обновлю пример подобной ситуацией для большей гибкости.

Или более простой способ:
можно не удалять лишние элементы списка, но при этом надо дописать в стили после

.voting:hover a.cur,
.voting.phover a.cur {
    background:none
}

вместо указанного в моем каменте выше вот это:

.inactive a:hover,
.inactive:hover a:hover,
.inactive.phover a:hover {
    background:none
}
.inactive:hover a.cur,
.inactive.phover a.cur:hover{
    background:#FC0 url('../../images/msk/images_ru/stars.png') no-repeat
}

не пойму для чего нужно voting:hover?
только что убрал его, работает отлично без никаких глюков, + для ie ничего не нужно придумывать
+ добавил опционально возможность неактивности звездочек после голоса, чтобы нельзя было повторно проголосовать
+ внутри невидимый input туда заносится результат

Селектор .voting:hover нужен для того, чтобы сбрасывать предустановленное значение рейтинга для пользователя, который хочет проголосовать. Мне кажется, что это весьма удобный механизм, когда происходит не просто клик по звёздочке без отдачи во время выбора, а именно такой динамический ряд.

Картинку звезд можно было сделать с прозрачным фоном(для большей универсальности)... а так интересное решение, можно заюзать при надобности :)

P.S. версия скрипта для JQuery

function setVote()
{
    $('.voting a').click(function(){
        $('.voting a').removeClass('cur');
        $(this).addClass('cur');
        $('#result').html('Rating is «' + this.rel + '»');
        return false;
    });
    if ($.browser.msie && $.browser.version < 8) {
        $('.voting li')
        .mouseover(function(){
            $('.voting li').addClass('phover');
            $('#result').html('mouseover');
        })
        .mouseout(function(){
            $('.voting li').removeClass('phover');
            $('#result').html('mouseout');
        });
    };
}
window.onload = setVote;

Вадим, у меня вопрос по этой теме в свете наступающего HTML5: уместно ли для таких рейтингов и подобных интерактивных мелочей использовать ?

P.S. Я тут ради прикола/тренировки решил попытаться реализовать рейтинг без метода GET и его недостатков (а заодно совсем без JS, без учета IE6-), тривиальным путем (позиционированием), и столкнулся с тем, что Опера 11.50 (Win 7 32b) почему-то отказалась применять :hover к позиционированным кнопкам (IE9 во всех режимах кроме Quirks, FF8, Chrome — применяют). В чем моя главная ошибка?

SelenIT, первый вопрос съел парсер, так что лучше положить код в <source>, как рекомендуется в подсказке. Что касается проблемы в Opera, то в 12-й альфа-версии этот баг уже поправлен — наверное можно поискать какой-нибудь обходной манёвр и для 11.x

Ой, сорри за мою невнимательность! Я имел в виду тег menu, который, по идее, изначально отличается от обычного списка именно интерактивностью. Для статичного рейтинга там еще вроде meter подходит (судя по примерам), но как быть с самой голосовалкой?

А насчет проблемы меня озадачило, что отдельно background у input:hover меняется, так что дело, похоже, в сочетании каких-то факторов. И спасибо за позитивную новость про 12-ю версию!

P.S. Понял причину той проблемы. Надо было просто уменьшить text-indent, чтобы он не вылетал за 32-кибипиксельный лимит :)

Кстати, да — достаточно поставить элементу overflow:hidden и необходимость в гигантских text-indent сразу отпадает ;)

Что касается правильного элемента, то мне кажется, что <meter> тут совсем мимо, ведь это вывод данных, а не ввод. Здесь скорее <input type="range"> по пяти фиксированным точкам.

Точно, про type="range" я и забыл, спасибо за напоминание!

P.S. Вот еще, тоже в порядке прикола, более лаконичный hReview-совместимый (вроде) вариант без JS и лишних тегов... но и без поддержки IE7 (для IE7, по идее, тоже можно "допилить" по аналогии через связку label+input, но уже "не так прикольно"). Вообще есть толк в таком подходе или это сразу неверный путь?