价格行为的滚动过渡动画。如何?

Scrolling transition animation for price action. How?

我在网上查了一下,只找到了 swift 的版本。在网上,returns 搜索结果的唯一关键字是 ScrollCounter。这种类型的动画是否可能 CSS HTML?

如果有人可以向我指出有效的资源或示例,我将在创建有效示例后post支持我的研究。

看看

并混淆 CSS 和 HTML...

更新 1

我找到了以下计数器动画,但仍在尝试弄清楚如何将其实现为货币计数器,包括用于大于 999.99 的数字的可选逗号。

尽管...在现实生活中,我会通过 WebSockets 更新此值。

const genNumber = () => {
  document.querySelector("div").style.setProperty("--percent", Math.random());
};

setInterval(genNumber, 3000);
setTimeout(genNumber);
@property --percent {
  syntax: "<number>";
  initial-value: 0;
  inherits: false;
}
@property --temp {
  syntax: "<number>";
  initial-value: 0;
  inherits: false;
}
@property --v1 {
  syntax: "<integer>";
  initial-value: 0;
  inherits: false;
}
@property --v2 {
  syntax: "<integer>";
  initial-value: 0;
  inherits: false;
}

div {
  font: 800 40px monospace;
  padding: 2rem;
  transition: --percent 1s;
  --temp: calc(var(--percent) * 100);
  --v1: max(var(--temp) - 0.5, 0);
  --v2: max((var(--temp) - var(--v1)) * 100 - 0.5, 0);
  counter-reset: v1 var(--v1) v2 var(--v2);
}
div::before {
  content: counter(v1) "." counter(v2, decimal-leading-zero) "%";
}
<div></div>

我修改了第一个答案,使其在语义上更合理。一路上添加了评论和解释,因为我发现@Phillip Rollins 的方法非常聪明。

// Usage

// Reference to div element that is going to be used as the spinner
const spinnerContainer = document.getElementById('BTCUSD');
spinnerContainer.setAttribute('class', 'spinner-container');

// INITITAL CONSTRUCT

// Array that contains div with class spinner-col HTML elements
const spinnerColumns = [];

// The View will contain the following possible characters
const spinnerCharacters = ['$', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', ','];

// Assumed total length of 12 digits...
for (let i = 0; i < 12; i++) {

  // Create Anonymous div
  spinnerColumns[i] = document.createElement('div');
  // Set Anonymous div class to spinner-col
  spinnerColumns[i].setAttribute('class', 'spinner-col');

  // For each character in array spinnerCharacters...
  for (let spinnerCharacter of spinnerCharacters) {

    // Create Anonymous div
    const div = document.createElement('div');
    // ..containing this character
    div.innerText = spinnerCharacter;
    // Set Anonymous div class to spinner-char
    div.setAttribute('class', 'spinner-char');

    // Append Anonymous div spinner-char to Anonymous div spinner-col
    spinnerColumns[i].append(div);

  }

  // Append Anonymous div spinner-col to spinner-container
  spinnerContainer.append(spinnerColumns[i]);
}



// RENDER LOGIC

// Set spinner value to number passed in
const setSpinnerValue = (spinnerValue) => {

  // The amount of column.style.display = 'none' attributes to initialize 
  // is equivalent to the amount of digits in the number passed in
  for (let i = spinnerValue.length; i < spinnerColumns.length; i++) {
    // initially NOT visible
    spinnerColumns[i].style.display = 'none';
  }

  // The amount of column.style.display = 'block' attributes to update 
  // is equivalent to the amount of digits in the number passed in
  for (let i = 0; i < spinnerValue.length; i++) {

    const index = spinnerCharacters.indexOf(spinnerValue[i]);
    // NOW visible
    spinnerColumns[i].style.display = 'block';
    // Apply CSS transform style
    spinnerColumns[i].style.transform = `translate(0, -${index * 75}px)`;
  }

};



// A currency formatter for setting the spinner value
const formatCurrency = new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'USD'
});



// PRICE FEED

// Just a timer setting random prices every 2 seconds
// Easily replaceble with polling or websockets
setInterval(() => {
  const spinnerValue = Math.random() * (999999 - 100000) + 100000;
  // Update Price 
  setSpinnerValue(formatCurrency.format(spinnerValue / 100));
}, 2000);
.spinner-container {
  height: 75px;
  display: flex;
  overflow: hidden;
}

.spinner-col {
  box-sizing: border-box;
  transform: translate(0, 0);
  transition: transform 800ms ease-in-out;
}

.spinner-char {
  width: 34px;
  text-align: center;
  font-size: 50px;
  height: 75px;
  line-height: 75px;
}
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-eOJMYsd53ii+scO/bJGFsiCZc+5NDVN2yr8+0RDqr0Ql0h+rP48ckxlpbzKgwra6" crossorigin="anonymous">

<div id="BTCUSD"></div>
<h4>BTCUSD</h4>

这是我设法想出的,希望比您自己的答案更符合您的要求。只需创建所有可能的字符,然后使用 CSS 将正确的字符滚动到视图中。

(() => {
  // BUILD VIEW
  const chars = ["$", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ".", ','];
  
  const formatter = new Intl.NumberFormat('en-US', {
    style: 'currency',
    currency: 'USD'
    });

  const container = document.createElement("div");
  container.setAttribute("class", "spinner-container");

  const cols = [];
  for (let x = 0; x < 12; x++) {
    cols[x] = document.createElement("div");
    cols[x].setAttribute("class", "spinner-col");

    for (let char of chars) {
      const div = document.createElement("div");
      div.innerText = char;
      div.setAttribute("class", "spinner-char");
      cols[x].append(div);
    }

    container.append(cols[x]);
  }
  document.body.append(container);

  // LOGIC

  const setValue = (value) => {
    for (let x = value.length; x < cols.length; x++) {
        cols[x].style.display = "none";
    }
    
    for (let x = 0; x < value.length; x++) {
      const index = chars.indexOf(value[x]);
      cols[x].style.display = "block";
      cols[x].style.transform = `translate(0, -${index * 75}px)`;
    }
  };

  setInterval(() => {
    const value = Math.random() * (999999 - 100000) + 100000;
    setValue(formatter.format(value / 100));
  }, 2000);
})();
.spinner-container {
  height: 75px;
  display: flex;
  overflow: hidden;
}

.spinner-col {
  box-sizing: border-box;
  transform: translate(0, 0);
  transition: transform 400ms ease-in-out;
}

.spinner-char {
  width: 34px;
  text-align: center;
  font-size: 50px;
  height: 75px;
  line-height: 75px;
}

抱歉双重回答,这是对我的另一个回答的扩展,不想增加原始回答的复杂性。使用自定义元素,我们可以使其可重复使用。

const chars = ["$", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ".", ','];

class RHPriceElement extends HTMLElement {
  static get observedAttributes() {
    return ['value'];
  }

  constructor() {
    super();
    this.formatter = new Intl.NumberFormat('en-US', {
      style: 'currency',
      currency: 'USD'
    });
    this._build();
  }

  attributeChangedCallback(name, _oldValue, newValue) {
    if (name === "value") {
      const value = this.formatter.format(newValue);

      while (this.cols.length < value.length) this._addCol();

      for (let x = value.length; x < this.cols.length; x++) {
        this.cols[x].style.display = "none";
      }

      for (let x = 0; x < value.length; x++) {
        const index = chars.indexOf(value[x]);
        this.cols[x].style.display = "block";
        this.cols[x].style.transform = `translate(0, -${index * 75}px)`;
      }
    }
  }

  _build() {
    this.cols = [];
    for (let x = 0; x < 12; x++) {
      this._addCol();
    }
  }

  _addCol() {
    const x = this.cols.length;
    this.cols[x] = document.createElement("div");
    this.cols[x].setAttribute("class", "spinner-col");

    for (let char of chars) {
      const div = document.createElement("div");
      div.innerText = char;
      div.setAttribute("class", "spinner-char");
      this.cols[x].append(div);
    }

    this.append(this.cols[x]);
  }
}

customElements.define('rh-price', RHPriceElement);

(() => {
  setInterval(() => {
    document.querySelectorAll("rh-price").item(0).setAttribute("value", Math.random() * (9999999 - 10000) + 10000);
    document.querySelectorAll("rh-price").item(1).setAttribute("value", Math.random() * (99999 - 1) + 1);
  }, 1000);
})();
rh-price {
  height: 75px;
  display: flex;
  overflow: hidden;
}

rh-price>div {
  transform: translate(0, 0);
  transition: transform 400ms ease-in-out;
}

rh-price>div>div {
  width: 34px;
  text-align: center;
  font-size: 50px;
  height: 75px;
  line-height: 75px;
}
<rh-price value="5000000"></rh-price>
<br>
<rh-price value="600"></rh-price>