如何通过 for 循环连接星级组件的评级栏标记?

问题描述 投票:0回答:5

我有 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 中正确显示评级数字,但循环中的渲染不会渲染该数字的基数

javascript string loops concatenation components
5个回答
2
投票

您的函数一旦第一次点击

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;
};

2
投票

注意@VLAZ 对这个问题的精彩评论:

const ratingHandler = productRate =>
  '<img src="assets/star.svg" alt="rating-star" />'.repeat(productRate+1)

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>


1
投票

为了好玩:也许是另一种(无循环,使用 css 背景图像)从评级创建星星的方法:

[编辑]

  • 在代码片段中添加了动态星级评级元素
  • 受到 Peter Seliger 的答案的启发,我创建了一个(恕我直言,不太复杂)网络组件用于“对星星进行排名”

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');
}


-2
投票

如果你想成为课堂上最酷的人,并且没有人应该理解你的代码,但它仍然应该完成它的工作,这就是你的选择:

const ratingHandler = (productRate) => [undefined].concat(productRate ?? []).reduce((all) => all += '<img src="assets/star.svg" alt="rating-star" />', "");

稍后谢谢我!

最新问题
© www.soinside.com 2019 - 2025. All rights reserved.