如何向 DOM 一种元素提供或定位不同的数据,以及如何管理/维护每个元素的重新渲染/更新周期?

How does one provide or target different data to DOM elements of one kind and does manage / maintain each of an element's rerender / update cycle?

我正在我的 wordpress 网站上的一个页面上工作,我从后端提取数据。数据有一个 end_date 列,我用它来显示倒数计时器,即从当前日期时间到 end_date

我是这样处理的:

这是将显示计时器的 html 元素:

<div class="t1-auction-timer"></div>

这是创建计时器的javascript:

var countDownDate = new Date("<?=$endDate?>").getTime();

var x = setInterval(function() {
    var now = new Date().getTime();
    
    var distance = countDownDate - now;
    
    var days = Math.floor(distance / (1000 * 60 * 60 * 24));
    var hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
    var minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
    var seconds = Math.floor((distance % (1000 * 60)) / 1000);
    
    var timer = document.querySelectorAll(".t1-auction-timer");
    
    for (var i = 0; i < timer.length; i++) 
    {
        timer[i].innerHTML = '<ul class="clock-value"><li class="clock-days">' + padNumber(days) + '<small>days</small> </li><li class="clock-separator">:</li><li class="clock-hours">  ' + padNumber(hours) + ' <small>hrs</small> </li><li class="clock-separator">:</li><li class="clock-minutes"> ' + padNumber(minutes) + ' <small>min</small> </li><li class="clock-separator">:</li><li class="clock-seconds"> ' + padNumber(seconds) + ' <small>sec</small> </li></ul>';
    }
    
    if ( distance < 0 )
    {
        clearInterval(x);
                    
        for (var i = 0; i < timer.length; i++) 
        {
            timer[i].innerHTML = "EXPIRED";
        }
    }
}, 1000);

function padNumber(number) 
{
    if( number == 0 )
    {
        number = "0" + number;
    }
    else if( number > 9 )
    {
        number;
    }
    else
    {
        number = "0" + number;    
    }

    return number;
}

<?=$endDate?>变量是从php传递过来的。它被正确传递。

根据数据库中的记录,要显示的计时器数量将是动态的。我面临的问题是:我只能从第一条记录中创建计时器。它在页面上显示的所有帖子中重复。我怀疑这与我的 .t1-auction-timer class 循环有关,但我还没有弄明白。

直接使用大量 OP 代码的方法几乎没有改变,在一个时间间隔内,通过一次又一次地(重新)渲染每个计时器来处理一组计时器元素。

计时器元素的标记基础将是上面(在评论中)提到的 <time/> 元素,其中 OP 的 endDate 将从该元素的 datetime 属性(也一次又一次)...

function padNumber(number) {
  return (number >= 10) && number || `0${ number }`;
}

function updateTimer(nodeList, timer, timerData) {
  // GUARD
  if (timer.dataset.isExpired === "true") {
    return;
  }
  const countDownDate = new Date(timer.dateTime).getTime();
  const now = new Date().getTime();
  const distance = countDownDate - now;

  const days = Math.floor(distance / (1000 * 60 * 60 * 24));
  const hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
  const minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
  const seconds = Math.floor((distance % (1000 * 60)) / 1000);
  
  if (distance >= 0) {
    timer.innerHTML = `<ul class="clock-value">
      <li class="clock-days">${ padNumber(days) } <small>days</small></li>
      <li class="clock-hours">${ padNumber(hours) } <small>hrs</small></li>
      <li class="clock-minutes">${ padNumber(minutes) } <small>min</small></li>
      <li class="clock-seconds">${ padNumber(seconds) } <small>sec</small></li>
     </ul>`;
  } else {
    timer.innerHTML = "EXPIRED";
    timer.dataset.isExpired = "true";

    // clear the interval only in case each timer has been expired.
    if ([
      ...nodeList
    ].every(elmNode =>
      elmNode.dataset.isExpired === "true"
    )) {
      clearInterval(timerData.timerId);
    }
  }
}     

function updateAllTimers(nodeList, timerData) {
  nodeList.forEach(timer => updateTimer(nodeList, timer, timerData));
}
function initializeAllTimers() {
  const nodeList = document.querySelectorAll('.t1-auction-timer');
  const data = {
    timerId: null
  };
  updateAllTimers(nodeList, data);

  data.timerId = setInterval(updateAllTimers, 1000, nodeList, data);
}

initializeAllTimers();
body {
  margin: 0;
}
ul {
  list-style: none;
  margin: 0;
  padding: 0;
}
li {
  padding: 2px 10px 3px 10px;
}
li .auction-name {
  font-family: courier;
}

ul.clock-value,
ul.clock-value > li {
  padding: 0;
  display: inline-block;
}
ul.clock-value > li {
  font-family: ui-monospace;
}
ul.clock-value li:not(:last-child) small:after {
  content: ":";
  display: inline-block;
  width: .7em;
  font-weight: bold;
  text-align: right;
}
<ul>
  <li>
    Auction <span class="auction-name">Foo</span> expires at/in ...
    <time class="t1-auction-timer" datetime="2020-12-21 22:15">2020-12-21 22:15</time>
  </li>
  <li>
    Auction <span class="auction-name">Bar</span> expires at/in ...
    <time class="t1-auction-timer" datetime="2020-12-22 03:15">2020-12-22 03:15</time>
  </li>
  <li>
    Auction <span class="auction-name">Baz</span> expires at/in ...
    <time class="t1-auction-timer" datetime="2020-12-22 11:30">2020-12-22 11:30</time>
  </li>
  <li>
    Auction <span class="auction-name">Biz</span> expires at/in ...
    <time class="t1-auction-timer" datetime="2020-12-22 14:45">2020-12-22 14:45</time>
  </li>
  <li>
    Auction <span class="auction-name">Buz</span> expires at/in ...
    <time class="t1-auction-timer" datetime="2020-12-23 10:20">2020-12-23 10:20</time>
  </li>
  <li>
    Auction <span class="auction-name">Fiz</span> expires at/in ...
    <time class="t1-auction-timer" datetime="2020-12-24 10:55">2020-12-24 10:55</time>
  </li>
  <li>
    Auction <span class="auction-name">Faz</span> expires at/in ...
    <time class="t1-auction-timer" datetime="2021-01-04 16:15">2021-01-04 16:15</time>
  </li>
</ul>

上述方法可能会更改为创建一个已经预渲染的计时器组件列表,其中每个组件的更新不再基于将一大块总是新创建的 HTML 标记分配给 DOM节点的innerHTML.

为了更通用/更灵活地使用此类组件,现在可以将自定义计时器标记的模板字符串以及自定义“已过期”消息传递给初始化方法。此外,任何组件现在都由其 data-countdown-component 属性标识,这使得创建/初始化过程独立于任何强制或仅与布局相关的 CSS class-names.

function emptyElementNode(elmNode) {
  Array
    .from(elmNode.childNodes)
    .forEach(node => node.remove());
}
function createTemplateFromMarkup(templateMarkup) {
  const container = document.createElement('div');
  container.innerHTML = String(templateMarkup);

  return container.firstElementChild;
}

function getTimeMeasureFromMilliseconds(value) {
  let days = (value / 3600000 / 24);  
  let hours = ((days - Math.floor(days)) * 24);
  let minutes = ((hours - Math.floor(hours)) * 60);
  let seconds = ((minutes - Math.floor(minutes)) * 60);

  days = Math.floor(days);
  hours = Math.floor(hours);
  minutes = Math.floor(minutes);
  seconds = Math.floor(seconds + 0.001);

  seconds = ((seconds < 60) && seconds) || 0;
  minutes = ((minutes < 60) && minutes) || 0;
  hours = ((hours < 24) && hours) || 0;

  return { days, hours, minutes, seconds };
}
function formatMeasure(value) {
  return ((value >= 10) && value || `0${ value }`);
}

function createTimerComponent(rootNode, template, expiredMessage) {
  const expirationDate = new Date(rootNode.dateTime);
  const [
    elmDays,
    elmHours,
    elmMinutes,
    elmSeconds
  ] = [...template.querySelectorAll([
    '[data-countdown-days]',
    '[data-countdown-hours]',
    '[data-countdown-minutes]',
    '[data-countdown-seconds]',
  ].join(','))];

  const component = {
    rootNode,
    elmDays,
    elmHours,
    elmMinutes,
    elmSeconds,
    isExpired: false,
    expiredMessage,
    expiresTimestamp: expirationDate.getTime(),
  };
  emptyElementNode(component.rootNode);

  rootNode.dateTime = expirationDate.toUTCString();
  rootNode.appendChild(template);

  return component;
}

function updateTimer(componentList, component, timerData) {
  // GUARD
  if (component.isExpired) {
    return;
  }
  const { dateNow } = timerData;
  const { expiresTimestamp } = component;
  const { elmDays, elmHours, elmMinutes, elmSeconds } = component;

  const time = (expiresTimestamp - dateNow);
  if (time >= 0) {
    const measures = getTimeMeasureFromMilliseconds(time);

    elmSeconds.textContent = formatMeasure(measures.seconds);
    elmMinutes.textContent = formatMeasure(measures.minutes);
    elmHours.textContent = formatMeasure(measures.hours);
    elmDays.textContent = formatMeasure(measures.days);

  } else {
    component.isExpired = true;

    emptyElementNode(component.rootNode);
    component.rootNode.textContent = component.expiredMessage;

    // clear the interval only in case each timer has been expired.
    if (componentList.every(item => item.isExpired)) {

      clearInterval(timerData.timerId);
    }
  }
}

function updateAllTimers(componentList, timerData) {
  timerData.dateNow = Date.now();

  componentList.forEach(component =>
    updateTimer(componentList, component, timerData)
  );
}
function initializeAllTimers(templateMarkup, expiredMessage = 'Closed') {
  const defaultTemplateMarkup = [
    '<span>',
    '<span data-countdown-days></span> <small>days</small>, ',
    '<span data-countdown-hours></span> <small>hours</small>, ',
    '<span data-countdown-minutes></span> <small>minutes</small>, ',
    '<span data-countdown-seconds></span> <small>seconds</small>',
    '</span>',
  ].join('');

  let template = createTemplateFromMarkup(templateMarkup);
  if (!template || template.querySelectorAll([

    '[data-countdown-days]',
    '[data-countdown-hours]',
    '[data-countdown-minutes]',
    '[data-countdown-seconds]',

  ].join(',')).length !== 4) {

    template = createTemplateFromMarkup(defaultTemplateMarkup);
  }
  const componentList = [
    ...document.querySelectorAll('[data-countdown-component]')
  ].map(elmNode =>
    createTimerComponent(elmNode, template.cloneNode(true), expiredMessage)
  );
  const data = {
    timerId: null,
    dateNow: null,
  };
  updateAllTimers(componentList, data);

  data.timerId = setInterval(updateAllTimers, 1000, componentList, data);
}

initializeAllTimers(`<ul class="clock-value">
  <li class="clock-days">
    <span data-countdown-days></span> <small>days</small>
  </li>
  <li class="clock-hours">
    <span data-countdown-hours></span> <small>hours</small>
  </li>
  <li class="clock-minutes">
    <span data-countdown-minutes></span> <small>minutes</small>
  </li>
  <li class="clock-seconds">
    <span data-countdown-seconds></span> <small>seconds</small>
  </li>
</ul>`, 'Expired');
body {
  margin: 0;
}
ul {
  list-style: none;
  margin: 0;
  padding: 0;
}
li {
  padding: 2px 10px 3px 10px;
}
li .auction-name {
  font-family: courier;
}

ul.clock-value,
ul.clock-value > li {
  padding: 0;
  display: inline-block;
}
ul.clock-value > li {
  font-family: ui-monospace;
}
ul.clock-value li:not(:last-child) small:after {
  content: ":";
  display: inline-block;
  width: .7em;
  font-weight: bold;
  text-align: right;
}
<ul>
  <li>
    Auction <span class="auction-name">Foo</span> expires at/in ...
    <time data-countdown-component datetime="2020-12-21 22:15">2020-12-21 22:15</time>
  </li>
  <li>
    Auction <span class="auction-name">Bar</span> expires at/in ...
    <time data-countdown-component datetime="2020-12-22 03:15">2020-12-22 03:15</time>
  </li>
  <li>
    Auction <span class="auction-name">Baz</span> expires at/in ...
    <time data-countdown-component datetime="2020-12-22 11:30">2020-12-22 11:30</time>
  </li>
  <li>
    Auction <span class="auction-name">Biz</span> expires at/in ...
    <time data-countdown-component datetime="2020-12-22 14:45">2020-12-22 14:45</time>
  </li>
  <li>
    Auction <span class="auction-name">Buz</span> expires at/in ...
    <time data-countdown-component datetime="2020-12-23 10:20">2020-12-23 10:20</time>
  </li>
  <li>
    Auction <span class="auction-name">Fiz</span> expires at/in ...
    <time data-countdown-component datetime="2020-12-24 10:55">2020-12-24 10:55</time>
  </li>
  <li>
    Auction <span class="auction-name">Faz</span> expires at/in ...
    <time data-countdown-component datetime="2021-01-04 16:15">2021-01-04 16:15</time>
  </li>
</ul>

您可以尝试这种使用 data 属性的方法。

var auctionTimer = document.getElementsByClassName('t1-auction-timer');

var interval = setInterval(function() {
  
if(auctionTimer.length == 0 ){clearFunction()}
  
for(var i= 0; i < auctionTimer.length; i++){
  var endDate = auctionTimer[i].getAttribute('data-end-date')
  var int = createTimer(endDate, auctionTimer[i]);
   }
},1000)


function clearFunction() {
  clearInterval(interval);
}


function createTimer(endDate, element){
  
var countDownDate = new Date(endDate).getTime();
var now = new Date().getTime();
var distance = countDownDate - now;
var days = Math.floor(distance / (1000 * 60 * 60 * 24));
var hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
var minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
var seconds = Math.floor((distance % (1000 * 60)) / 1000);
element.innerHTML = '<ul class="clock-value"><li class="clock-days">' + padNumber(days) + '<small>days</small> </li><li class="clock-separator">:</li><li class="clock-hours">  ' + padNumber(hours) + ' <small>hrs</small> </li><li class="clock-separator">:</li><li class="clock-minutes"> ' + padNumber(minutes) + ' <small>min</small> </li><li class="clock-separator">:</li><li class="clock-seconds"> ' + padNumber(seconds) + ' <small>sec</small> </li></ul>';

  if ( distance < 0 )
{
    if (  element.classList.contains("t1-auction-timer") ){
          element.classList.remove("t1-auction-timer")
          element.classList.add("expierd")
          expierd()
    
    }
}
}       
function padNumber(number) 
{
if( number == 0 )
{
    number = "0" + number;
}
else if( number > 9 )
{
    number;
}
else
{
    number = "0" + number;    
}

return number;
}

function expierd(){
  var expierdDate = document.getElementsByClassName('expierd');
  for(var i= 0; i < expierdDate.length; i++){
expierdDate[i].innerHTML = "expierd"
   }
}
<div class="t1-auction-timer" data-end-date="2021-01-03 16:15"></div>
<div class="t1-auction-timer" data-end-date="2021-01-01 16:15"></div>
<div class="t1-auction-timer" data-end-date="2021-01-04 16:15"></div>