Анимируйте огромное количество элементов с помощью JS-анимации CSS-перехода. - PullRequest
0 голосов
/ 02 июля 2019

Отличие от попиксельной «HTML-анимации» заключается в том, что «CSS-анимации» нужен только Javascript для установки точки, в которой CSS-переход заботится о промежуточных перемещениях. , Динамическая установка нескольких точек является альтернативой CSS-анимации , но имеет гораздо больший контроль над кодом и взаимодействием.

У меня есть следующий код, где я хочу, чтобы drag-zoom имел плавный переход CSS.

<html>
  <head>
  </head>
  <body>


  <div id="waterfall"></div>


  <script>

    ////////////////////////////////////////////////////////////////////////////////////
    // draw

    var fileData, max_zoom_view_value;
    var view_A, view_B, exampleSize = 3;
    var ratio_A, ratio_B;

    function rangePercent(a, b) {
      return 100*(b - a)/(max_zoom_view_value)
    }

    function simulate_data() {
        fileData = ["foo.js", "bar.js", "style.css", "image.png", "script.js", "my_mod.js", "img_123.jpg"];
        for (var i = 0; i < exampleSize; i++) fileData = fileData.concat(fileData); //make example data bigger
        max_zoom_view_value = 0
        fileData.forEach((x, i) => { //add more data
          var a = i*100, b = i*100 + 100; //simulate bars start and stop positions
          fileData[i] = {key: fileData[i], start: a, stop: b}
          if (b > max_zoom_view_value) max_zoom_view_value = b
        })
        ratio_A = 0.001; ratio_B = 1;
    }

//factor ratio fraction range view
    function draw() {
        var htm = "<table cellpadding='5'><tr style = 'text-align:center; font-weight:bold;'><td>file</td><td>type</td><td>time</td><td id='zoomZone'>waterfall</td></tr>";
        var zoomSelectField = "<div id='zoomField' style='position:absolute; height:100%; background:gray; opacity:.2;'></div>";

        view_A = ratio_A * max_zoom_view_value
        view_B = ratio_B * max_zoom_view_value
        max_zoom_view_value = view_B - view_A //zoomed in

        for (let i = 0; i < fileData.length; i++) {
          var x = fileData[i], name = x.key, type = name.split('.')[1];
          var start = x.start, stop = x.stop;

///////////

          /*          ............view............
                      A                          B
                      :                          :
              ...][cut|  ][...bars in view...][  |cut]...
                  :      :                    :      :
                start  stop                 start  stop
          */
          var A = start, B = stop; //bar positions in view
          if (start < view_A) { A = view_A } //cut
          if (stop > view_B) { B = view_B } //cut
          if (stop < view_A || start > view_B) A = B = 0; //erase bars outside view

////////////

          var barAlign = rangePercent(0, A - view_A), barWidth = rangePercent(A, B);
          htm += "<tr><td>" + name + "</td><td>" + type + "</td><td>" + parseInt(stop - start) + " ms</td>";
          htm += "<td id='id" + i + "' style='transition: width 2s; cursor:pointer; width:100%; overflow:hidden;'>";
          htm += "<div class='bar' style='margin-left:" + barAlign + "%; width:" + barWidth + "%;'>&nbsp;<sub>" + parseInt(start) + " - " + parseInt(stop) + "</sub></div></td></tr>";
        }

        document.querySelector('#waterfall').innerHTML = "<div class='waterfall noselect' onmousemove='mousemove(event)'>" + zoomSelectField + htm + "</table></div>"
        // https://jsperf.com/fragments-vs-html-strings
        // https://jsperf.com/document-fragment-test-peluchetti

        zoomZone = document.querySelector('#zoomZone')
        zoomField = document.querySelector('#zoomField')

    }

    function draw2() {
        for (let i = 0; i < fileData.length; i++) {
          var x = document.querySelector('#id' + i)
          x.style.width = "400px"
        }
    }

    setTimeout(draw2, 5000)

    ////////////////////////////////////////////////////////////////////////////////////


    var style = document.createElement('style');
    style.innerHTML = "*{font-family:arial, sans-serif;} .waterfall{position: relative;} .noselect {-webkit-touch-callout: none;-webkit-user-select: none;-khtml-user-select: none;-moz-user-select: none;-ms-user-select: none;user-select: none;} .bar{display:inline-block; background-color: lightGray;} table{border-collapse:collapse; white-space:nowrap;} td{border: solid 1px gray} ";
    document.head.appendChild(style);

    ////////////////////////////////////////////////////////////////////////////////////
    // zoom

    var zoomZone, zoomField, mouseX, fixedX, bod = document.body, mouseDown = 0;

    function mousemove(e) {
      mouseX = e.clientX;
      var w = mouseDown? Math.abs(mouseX - fixedX): 1
      zoomField.style.width = w + 'px'
      zoomField.style.left = (mouseX > fixedX? mouseX - w: mouseX) + 'px'
      if (mouseX < zoomZone.offsetLeft) redraw()
    }

    bod.onmousedown = select
    bod.onmouseup = startZoom
    document.onmouseleave = redraw

    function select() {
      mouseDown = 1;
      fixedX = mouseX
    }

    function redraw() {
      cancel()
      simulate_data()
      draw()
    }

    function cancel() {
      mouseDown = 0;
      zoomField.style.width = '0px'
    }

    ////////////////////////////////////////////////////////////////////////////////////
    // CSS transition

    function startZoom(e) {
      if (e.clientX == fixedX) redraw()
      else {
        var A = mouseX - zoomZone.offsetLeft, B = fixedX - zoomZone.offsetLeft
        if (A > B) { var swap = A; A = B; B = swap; }
        ratio_A = A/zoomZone.offsetWidth
        ratio_B = B/zoomZone.offsetWidth
        cancel()
        draw()
      }
    }

    simulate_data()
    draw()

  </script>

  </body>
</html>

Итак, первое, что я пытаюсь сделать, - это минимальное «доказательство концепции»:

  • Найдите рабочий фрагмент и скопируйте transition: width 2s;
  • Вставьте его в <td id='id" + i + "' style='transition: width 2s; .... Также сделан уникальный идентификатор для изменения ширины позже на document.querySelector('#id' + i).style.width = "400px", через пять секунд. Смотрите код.
  • Запустите фрагмент кода и подождите 5 секунд. Переход не гладкий!

Как я могу заставить CSS переход работать? Или CSS анимация? Какие есть альтернативы? Ширина должна быть установлена ​​динамически, и 400 - только пример. Я думаю, что простая анимация слишком медленная.

Обновление

Как указал @GramThanos, он работает, когда пиксели и процент не смешаны, как в неизменном фрагменте выше. Изменение "400px" на "50%" будет плавно уменьшать масштаб. Но увеличение на «200%» не сработало!

Обновление 2

A <td> невозможно увеличить на «200%»! Переместите его во внутренний <div>, и он работает. overflow:hidden затем режет то, что выходит за пределы <td>.

Название изменено на более общее, поскольку в общем случае при любых перемещениях и свойствах для анимации это используемое решение также может работать:

<div id='id" + i + "' style='width:100%; transition: width 2s, margin-left 2s;'>

width и margin-left вы меняете на то, что вам нужно. Несколько свойств для анимации разделяются запятой. Можно установить другие начальные значения, чем по умолчанию. Например, здесь width:100%, чтобы быть уверенным, и margin-left уже 0 и не должны упоминаться в качестве начального значения.

Требуется установка уникального идентификатора для каждого элемента, чтобы иметь Javascript для изменения стиля элементов, например:

setTimeout(draw2,4000)
function draw2() {
    for (let i = 0; i < fileData.length; i++) {
      var x = document.querySelector('#id' + i)
      x.style.width = "200%"
      x.style.marginLeft = "-50%"
    }
}

Обратите внимание, что в JS любой - должен быть удален, поэтому margin-left должен быть записан marginLeft. Здесь масштаб увеличивается одинаково влево и вправо на каждом #id. Чтобы получить нужные ходы, может понадобиться ручка и бумага, а также некоторые расчеты.

Вот полный код:

<html>
  <head>
  </head>
  <body>


  <div id="waterfall"></div>


  <script>

    ////////////////////////////////////////////////////////////////////////////////////
    // draw

    var fileData, max_zoom_view_value;
    var view_A, view_B, exampleSize = 3;
    var ratio_A, ratio_B;

    function rangePercent(a, b) {
      return 100*(b - a)/(max_zoom_view_value)
    }

    function simulate_data() {
        fileData = ["foo.js", "bar.js", "style.css", "image.png", "script.js", "my_mod.js", "img_123.jpg"];
        for (var i = 0; i < exampleSize; i++) fileData = fileData.concat(fileData); //make example data bigger
        max_zoom_view_value = 0
        fileData.forEach((x, i) => { //add more data
          var a = i*100, b = i*100 + 100; //simulate bars start and stop positions
          fileData[i] = {key: fileData[i], start: a, stop: b}
          if (b > max_zoom_view_value) max_zoom_view_value = b
        })
        ratio_A = 0.001; ratio_B = 1;
    }

//factor ratio fraction range view
    function draw() {
        var htm = "<table cellpadding='5'><tr style = 'text-align:center; font-weight:bold;'><td>file</td><td>type</td><td>time</td><td id='zoomZone'>waterfall</td></tr>";
        var zoomSelectField = "<div id='zoomField' style='position:absolute; height:100%; background:gray; opacity:.2;'></div>";

        view_A = ratio_A * max_zoom_view_value
        view_B = ratio_B * max_zoom_view_value
        max_zoom_view_value = view_B - view_A //zoomed in

        for (let i = 0; i < fileData.length; i++) {
          var x = fileData[i], name = x.key, type = name.split('.')[1];
          var start = x.start, stop = x.stop;

///////////

          /*          ............view............
                      A                          B
                      :                          :
              ...][cut|  ][...bars in view...][  |cut]...
                  :      :                    :      :
                start  stop                 start  stop
          */
          var A = start, B = stop; //bar positions in view
          if (start < view_A) { A = view_A } //cut
          if (stop > view_B) { B = view_B } //cut
          if (stop < view_A || start > view_B) A = B = 0; //erase bars outside view

////////////

          var barAlign = rangePercent(0, A - view_A), barWidth = rangePercent(A, B);
          htm += "<tr><td>" + name + "</td><td>" + type + "</td><td>" + parseInt(stop - start) + " ms</td>";
          htm += "<td style='cursor:pointer; width:100%; overflow:hidden;'><div id='id" + i + "' style='width:100%; transition: width 2s, margin-left 2s;'>";
          htm += "<div class='bar' style='margin-left:" + barAlign + "%; width:" + barWidth + "%;'>&nbsp;<sub>" + parseInt(start) + " - " + parseInt(stop) + "</sub></div></div></td></tr>";
        }

        document.querySelector('#waterfall').innerHTML = "<div class='waterfall noselect' onmousemove='mousemove(event)'>" + zoomSelectField + htm + "</table></div>"
        // https://jsperf.com/fragments-vs-html-strings
        // https://jsperf.com/document-fragment-test-peluchetti

        zoomZone = document.querySelector('#zoomZone')
        zoomField = document.querySelector('#zoomField')

    }

    function draw2() {
        for (let i = 0; i < fileData.length; i++) {
          var x = document.querySelector('#id' + i)
          x.style.width = "200%"
          x.style.marginLeft = "-50%"
        }
    }

    setTimeout(draw2,4000)

    ////////////////////////////////////////////////////////////////////////////////////


    var style = document.createElement('style');
    style.innerHTML = "*{font-family:arial, sans-serif;} .waterfall{position: relative;} .noselect {-webkit-touch-callout: none;-webkit-user-select: none;-khtml-user-select: none;-moz-user-select: none;-ms-user-select: none;user-select: none;} .bar{display:inline-block; background-color: lightGray;} table{border-collapse:collapse; white-space:nowrap;} td{border: solid 1px gray} ";
    document.head.appendChild(style);

    ////////////////////////////////////////////////////////////////////////////////////
    // zoom

    var zoomZone, zoomField, mouseX, fixedX, bod = document.body, mouseDown = 0;

    function mousemove(e) {
      mouseX = e.clientX;
      var w = mouseDown? Math.abs(mouseX - fixedX): 1
      zoomField.style.width = w + 'px'
      zoomField.style.left = (mouseX > fixedX? mouseX - w: mouseX) + 'px'
      if (mouseX < zoomZone.offsetLeft) redraw()
    }

    bod.onmousedown = select
    bod.onmouseup = startZoom
    document.onmouseleave = redraw

    function select() {
      mouseDown = 1;
      fixedX = mouseX
    }

    function redraw() {
      cancel()
      simulate_data()
      draw()
    }

    function cancel() {
      mouseDown = 0;
      zoomField.style.width = '0px'
    }

    ////////////////////////////////////////////////////////////////////////////////////
    // CSS transition

    function startZoom(e) {
      if (e.clientX == fixedX) redraw()
      else {
        var A = mouseX - zoomZone.offsetLeft, B = fixedX - zoomZone.offsetLeft
        if (A > B) { var swap = A; A = B; B = swap; }
        ratio_A = A/zoomZone.offsetWidth
        ratio_B = B/zoomZone.offsetWidth
        cancel()
        draw()
      }
    }

    simulate_data()
    draw()

  </script>

  </body>
</html>
...