我有 3 种不同产品的评级编号, 我正在尝试渲染他们的评级数的星形 svg 基础。 例如产品A的速率为3,我想渲染star img 3次。 他们最多可以拥有 5 颗星星。
我写了这样的代码
const ratingHandler = (productRate) => {
for (let i = 0; i < productRate + 1; i++) {
console.log(productRate);
return `<img src="assets/star.svg" alt="rating-star" />`;
}
};
产品率在我的 console.log 中正确显示评级数字,但循环中的渲染不会渲染该数字的基数
您的函数一旦第一次点击
return
就会结束。您需要在返回之前构建完整的字符串:
const ratingHandler = (productRate) => {
let stars = '';
for (let i = 0; i < productRate + 1; i++) {
stars += `<img src="assets/star.svg" alt="rating-star" />`;
}
return stars;
};
注意@VLAZ 对这个问题的精彩评论:
const ratingHandler = productRate =>
'<img src="assets/star.svg" alt="rating-star" />'.repeat(productRate+1)
从我上面的评论...
OP 的实际问题要深刻得多。星级评定应该被视为一个组件,一个问题的抽象,一般只实现一次。因此,我们的目标是开发一种可重用的组件,可以轻松地通过属性值更改和 CSS 更改来适应它。它的标记可以灵活处理,即使在语义上比 OP 的星图序列更正确。
第一步是想出一个有意义的标记和一些 CSS ...还没有涉及 JavaScript。
[data-star-rating] {
position: relative;
/* display: inline-block; */
display: block;
/* margin: 0; */
margin: 8px 0;
> p,
> span {
display: inline-block;
margin: 0 4px 0 0;
&:after {
content: ':'
}
}
> output,
> figcaption {
position: relative;
display: inline-block;
margin: 0;
color: transparent;
&:after,
&:before {
position: absolute;
left: 0;
top: -8px;
content: '';
font-size: 1.3em;
letter-spacing: 2px;
-webkit-text-stroke: 1px black;
}
&:after {
color: white;
}
&:before {
z-index: 1;
color: gold;
}
&[data-max-amount="3"] {
&:after {
content: '★★★';
}
}
&[data-max-amount="4"] {
&:after {
content: '★★★★';
}
}
&[data-max-amount="5"] {
&:after {
content: '★★★★★';
}
}
&[data-max-amount="6"] {
&:after {
content: '★★★★★★';
}
}
&[data-max-amount="7"] {
&:after {
content: '★★★★★★★';
}
}
&[data-amount="1"] {
&:before {
content: '★';
}
}
&[data-amount="2"] {
&:before {
content: '★★';
}
}
&[data-amount="3"] {
&:before {
content: '★★★';
}
}
&[data-amount="4"] {
&:before {
content: '★★★★';
}
}
&[data-amount="5"] {
&:before {
content: '★★★★★';
}
}
&[data-amount="6"] {
&:before {
content: '★★★★★★';
}
}
&[data-amount="7"] {
&:before {
content: '★★★★★★★';
}
}
}
}
<figure data-star-rating>
<p>Rating</p>
<figcaption data-amount="4" data-max-amount="7" title="4 of 7 stars">
4 of 7 stars
</figcaption>
</figure>
<label data-star-rating>
<span>Rating</span>
<output data-amount="2" data-max-amount="5" title="2 of 5 stars">
2 of 5 stars
</output>
</label>
<figure data-star-rating>
<p>Rating</p>
<figcaption data-amount="1" data-max-amount="4" title="1 of 4 stars">
1 of 4 stars
</figcaption>
</figure>
<label data-star-rating>
<span>Rating</span>
<output data-amount="3" data-max-amount="3" title="3 of 3 stars">
3 of 3 stars
</output>
</label>
<figure data-star-rating>
<p>Rating</p>
<figcaption data-amount="5" data-max-amount="6" title="5 of 6 stars">
5 of 6 stars
</figcaption>
</figure>
下一次迭代在使用(可以省略/显示评级副本)、语义和可访问性方面提供了更好的标记。前面显示的 CSS 规则已根据这个新的 HTML 结构进行了调整。顶部是 JavaScript,它实现了
star-rating
web-component。
该实现作为自主的自定义元素,它必须继承自 HTML 元素基类
HTMLElement
。这是由于 Apple 缺乏对可以继承标准 HTML 元素的自定义内置元素的支持。
人们可以通过利用像
<figure is="star-rating"/>
这样的咏叹调角色来解决无法使用 <star-rating aria-role="figure" />
的语义差距。
以下示例代码具有一个附加脚本,允许操作每个星级组件,以证明组件的概念和正确的实现。
每个星级组件都可以通过提供和/或变异/更改两个自定义
data-*
属性值来使用。就这么简单。
function parseDataKey(attrName) {
return attrName
.replace(/^data-/, '')
.replace(/-(\p{Ll})/gu, (_, char) => char.toUpperCase());
}
function getApprovedAmountValue(initialData, dataKey, latestValue) {
const initialValue = initialData[dataKey];
latestValue = Number(latestValue);
latestValue = Number.isFinite(latestValue) ? latestValue : initialValue;
return String(
approvedValue = (dataKey === 'maxAmount')
&& Math.max(3, Math.min(latestValue, initialValue))
|| Math.max(0, Math.min(latestValue, initialData['maxAmount'] ?? latestValue))
);
}
class StarRating extends HTMLElement {
static observedAttributes = ['data-amount', 'data-max-amount'];
#initialData = {};
#captionNode;
constructor() {
super();
Object.assign(this.#initialData, {
amount: null,
maxAmount: null,
})
this.#captionNode = this.querySelector('[data-template-target]');
}
renderCaption() {
const { dataset: { template, amount, maxAmount, } } = this;
const ratingCopy = template
.replace(/\${\samount\s\}/g, amount)
.replace(/\${\smaxAmount\s\}/g, maxAmount);
this.#captionNode.textContent = ratingCopy;
this.title = ratingCopy;
}
attributeChangedCallback(attrName, recentValue, currentValue) {
const dataKey = parseDataKey(attrName);
if (recentValue === null) {
this.#initialData[dataKey] = currentValue;
}
const approvedValue =
getApprovedAmountValue(this.#initialData, dataKey, currentValue);
if (approvedValue !== currentValue) {
// set correct value and indirectly trigger method execution again .
this.dataset[dataKey] = approvedValue;
} else {
// rendering of valid/sanitized value changes only.
this.renderCaption();
}
}
}
customElements.define("star-rating", StarRating);
star-rating/*, [is="star-rating"]*/ {
position: relative;
display: block;
margin: 12px 0;
> figcaption:has(> [data-template-target]) {
position: relative;
float: left;
margin: 0;
padding: 1px 4px 0 0;
> [data-template-target] {
overflow: hidden;
position: absolute;
width: 0;
&.visible {
overflow: unset;
position: unset;
width: unset;
}
}
}
> [data-figure] {
display: inline-block;
position: relative;
top: -1.3em;
&:after,
&:before {
position: absolute;
left: 0;
top: 0;
content: '';
font-size: 1.3em;
letter-spacing: 2px;
-webkit-text-stroke: 1px black;
}
&:after {
color: white;
}
&:before {
z-index: 1;
color: gold;
}
}
&[data-max-amount="3"] {
> [data-figure] {
&:after {
content: '★★★';
}
}
}
&[data-max-amount="4"] {
> [data-figure] {
&:after {
content: '★★★★';
}
}
}
&[data-max-amount="5"] {
> [data-figure] {
&:after {
content: '★★★★★';
}
}
}
&[data-max-amount="6"] {
> [data-figure] {
&:after {
content: '★★★★★★';
}
}
}
&[data-max-amount="7"] {
> [data-figure] {
&:after {
content: '★★★★★★★';
}
}
}
&[data-amount="1"] {
> [data-figure] {
&:before {
content: '★';
}
}
}
&[data-amount="2"] {
> [data-figure] {
&:before {
content: '★★';
}
}
}
&[data-amount="3"] {
> [data-figure] {
&:before {
content: '★★★';
}
}
}
&[data-amount="4"] {
> [data-figure] {
&:before {
content: '★★★★';
}
}
}
&[data-amount="5"] {
> [data-figure] {
&:before {
content: '★★★★★';
}
}
}
&[data-amount="6"] {
> [data-figure] {
&:before {
content: '★★★★★★';
}
}
}
&[data-amount="7"] {
> [data-figure] {
&:before {
content: '★★★★★★★';
}
}
}
}
fieldset { margin: 24px 0 0 0; label { margin: 0 8px 0 0; } }
<star-rating aria-role="figure"
data-amount="1"
data-max-amount="4"
data-template="${ amount } of ${ maxAmount } stars">
<figcaption>
Rating:
<span data-template-target class="visible">-- of -- stars</span> /
</figcaption>
<div data-figure></div>
</star-rating>
<star-rating aria-role="figure"
data-amount="3"
data-max-amount="3"
data-template="${ amount } of ${ maxAmount } stars">
<figcaption>
Rating
... <span data-template-target class="visible">-- of -- stars</span> ...
</figcaption>
<div data-figure></div>
</star-rating>
<star-rating aria-role="figure"
data-amount="5"
data-max-amount="7"
data-template="${ amount } of ${ maxAmount } stars">
<figcaption>
Rating:
<span data-template-target>-- of -- stars</span>
</figcaption>
<div data-figure></div>
</star-rating>
<fieldset>
<legend>Change Star Ratings</legend>
<label>
<span>1st Rating</span>
<select data-idx="0">
<option value="0">none</option>
<option value="1" selected>★</option>
<option value="2">★★</option>
<option value="3">★★★</option>
<option value="4">★★★★</option>
</select>
</label>
<label>
<span>2nd Rating</span>
<select data-idx="1">
<option value="0">none</option>
<option value="1">★</option>
<option value="2">★★</option>
<option value="3" selected>★★★</option>
</select>
</label>
<label>
<span>3rd Rating</span>
<select data-idx="2">
<option value="0">none</option>
<option value="1">★</option>
<option value="2">★★</option>
<option value="3">★★★</option>
<option value="4">★★★★</option>
<option value="5" selected>★★★★★</option>
<option value="6">★★★★★★</option>
<option value="7">★★★★★★★</option>
</select>
</label>
</fieldset>
<script>
document
.querySelector('fieldset')
.addEventListener('change', ({ target }) => {
document
.querySelectorAll('star-rating')
.item(target.dataset.idx)
.dataset.amount = target.value;
});
</script>
为了好玩:也许是另一种(无循环,使用 css 背景图像)从评级创建星星的方法:
[编辑]
const toBody = html => document.body.insertAdjacentHTML(`beforeend`, html);
document.addEventListener(`click`, handle);
[0, 4, 3, 1, 5, 2].forEach(v => toBody(rate(v)));
document.body.insertAdjacentHTML(
`beforeend`,
`<div class="rateMe" data-maxrate="10">
${rate(0, 10, true)}
<div><button data-reset='1'>reset</button></div>
</div> `
);
function rate(rating, n = 5) {
return `<div data-nstars="${n}" data-rating="${rating}">
${`<span class="star active"></span>`.repeat(rating)}${
`<span class="star inactive"></span>`.repeat(n-rating)}</div>`;
}
function handle(evt) {
const rateElement = evt.target.closest(`.rateMe`);
if (rateElement) {
const maxRate = +rateElement.dataset.maxrate;
const stars = rateElement.querySelectorAll(`.star`);
rateElement.querySelector(`[data-rating]`).remove();
const score = evt.target.dataset.reset ? 0 :
[...stars].reduce((acc, s, i) => s === evt.target ? acc + i : acc, 1);
return rateElement.insertAdjacentHTML(`afterBegin`, rate(score, maxRate));
}
}
[data-rating]:before {
content: attr(data-rating)'/'attr(data-nstars);
color: green;
}
.star {
background-repeat: no-repeat;
background-size: cover;
width: 16px;
height: 16px;
display: inline-block;
margin-left: 3px;
}
.rateMe:before {
content: 'Click to rate';
display: block;
margin: 1rem 0 0.2rem 0;
font-weight: bold;
}
.rateMe div {
cursor: pointer;
display: inline-block;
}
.inactive {
background-image: url('data:image/svg+xml,%3Csvg%20height%3D%2250px%22%20width%3D%2250px%22%20version%3D%221.1%22%20id%3D%22Capa_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20viewBox%3D%220%200%2047.94%2047.94%22%20xml%3Aspace%3D%22preserve%22%20fill%3D%22%23000000%22%20stroke%3D%22%23000000%22%20stroke-width%3D%220.62322%22%3E%3Cg%20id%3D%22SVGRepo_bgCarrier%22%20stroke-width%3D%220%22%3E%3C%2Fg%3E%3Cg%20id%3D%22SVGRepo_tracerCarrier%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3C%2Fg%3E%3Cg%20id%3D%22SVGRepo_iconCarrier%22%3E%20%3Cpath%20style%3D%22fill%3A%23FFFFFF%3B%22%20d%3D%22M26.285%2C2.486l5.407%2C10.956c0.376%2C0.762%2C1.103%2C1.29%2C1.944%2C1.412l12.091%2C1.757%20c2.118%2C0.308%2C2.963%2C2.91%2C1.431%2C4.403l-8.749%2C8.528c-0.608%2C0.593-0.886%2C1.448-0.742%2C2.285l2.065%2C12.042%20c0.362%2C2.109-1.852%2C3.717-3.746%2C2.722l-10.814-5.685c-0.752-0.395-1.651-0.395-2.403%2C0l-10.814%2C5.685%20c-1.894%2C0.996-4.108-0.613-3.746-2.722l2.065-12.042c0.144-0.837-0.134-1.692-0.742-2.285l-8.749-8.528%20c-1.532-1.494-0.687-4.096%2C1.431-4.403l12.091-1.757c0.841-0.122%2C1.568-0.65%2C1.944-1.412l5.407-10.956%20C22.602%2C0.567%2C25.338%2C0.567%2C26.285%2C2.486z%22%3E%3C%2Fpath%3E%3C%2Fg%3E%3C%2Fsvg%3E');
}
.active, .rateMe .star:hover {
background-image: url('data:image/svg+xml,%3Csvg%20height%3D%2250px%22%20width%3D%2250px%22%20version%3D%221.1%22%20id%3D%22Capa_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20viewBox%3D%220%200%2047.94%2047.94%22%20xml%3Aspace%3D%22preserve%22%20fill%3D%22%23000000%22%20stroke%3D%22%23000000%22%20stroke-width%3D%220.62322%22%3E%3Cg%20id%3D%22SVGRepo_bgCarrier%22%20stroke-width%3D%220%22%3E%3C%2Fg%3E%3Cg%20id%3D%22SVGRepo_tracerCarrier%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3C%2Fg%3E%3Cg%20id%3D%22SVGRepo_iconCarrier%22%3E%20%3Cpath%20style%3D%22fill%3A%23ED8A19%3B%22%20d%3D%22M26.285%2C2.486l5.407%2C10.956c0.376%2C0.762%2C1.103%2C1.29%2C1.944%2C1.412l12.091%2C1.757%20c2.118%2C0.308%2C2.963%2C2.91%2C1.431%2C4.403l-8.749%2C8.528c-0.608%2C0.593-0.886%2C1.448-0.742%2C2.285l2.065%2C12.042%20c0.362%2C2.109-1.852%2C3.717-3.746%2C2.722l-10.814-5.685c-0.752-0.395-1.651-0.395-2.403%2C0l-10.814%2C5.685%20c-1.894%2C0.996-4.108-0.613-3.746-2.722l2.065-12.042c0.144-0.837-0.134-1.692-0.742-2.285l-8.749-8.528%20c-1.532-1.494-0.687-4.096%2C1.431-4.403l12.091-1.757c0.841-0.122%2C1.568-0.65%2C1.944-1.412l5.407-10.956%20C22.602%2C0.567%2C25.338%2C0.567%2C26.285%2C2.486z%22%3E%3C%2Fpath%3E%3C%2Fg%3E%3C%2Fsvg%3E');
}
如果你想成为课堂上最酷的人,并且没有人应该理解你的代码,但它仍然应该完成它的工作,这就是你的选择:
const ratingHandler = (productRate) => [undefined].concat(productRate ?? []).reduce((all) => all += '<img src="assets/star.svg" alt="rating-star" />', "");
稍后谢谢我!