Используйте Canvas для рисования компонента настраиваемой шкалы (линейки) [действующий полный набор]

JavaScript
Используйте Canvas для рисования компонента настраиваемой шкалы (линейки) [действующий полный набор]

предисловие

На этот раз я делюсь компонентом настраиваемого масштаба (линейки), нарисованным мной с помощью Canvas. Компонентное приложение: в основном используется для скользящего выбора значений мобильного терминала, сумм и т. д., чтобы улучшить взаимодействие с пользователем.

Статья предполагает знание идей разработки, дизассемблирования кода, решения проблем, инкапсуляции библиотеки компонентов масштабирования и т. д. Детская обувь, нуждающаяся в помощи, может быть правильно названа для разработки более блестящих и совершенных компонентов.

Добро пожаловать на адрес github, начните много:GitHub.com/now1 слишком безжалостен/руб…;

Также приглашаем вас посетить функцию демо-адреса:rnlvwyx.cn:3333/#/demo;

Описание функционального анализа

Демонстрация масштабируемого компонента, рендеринг:

demo2.gif
Из анализа диаграммы эффекта видно, что центральная линия не перемещается посередине; отображается эффект длины шкалы интервала и значение шкалы шкалы каждые 10 интервалов, и шкала может перемещаться влево и вправо до выберите значение; переместитесь влево от минимального значения и вправо После достижения максимального значения шкала не может продолжать движение; она поддерживает множество элементов оптимизации на основе входящего значения, реагирования на изменения шкалы и сигнатур.

Разработка компонента будет выполнять следующие функции:

  • Используйте холст для рисования компонентов, чтобы решить проблему размытия мобильного рисования,
  • Поддержка ввода базовой конфигурации параметров весов,
  • Отслеживание событий скольжения, вывод значения масштаба в режиме реального времени при скольжении и поддержка динамической настройки масштаба в соответствии с внешними значениями,
  • Поддержка плавного / плавного скольжения, масштабирование в реальном времени,
  • Совместимость с мобильным / компьютерным слайдером,

Использование компонентов

Импортируйте файл scale.js:
import scale from './scale.js'; // 引入scale.js文件
// or
npm install canvas-scale
import scale from 'canvas-scale';

Модуль масштабирования предоставляет внешнему миру метод инициализации init();scale.init()函数 :

  • Первый параметр является проходимымdocument.querySelector()Получить узел HTML;
  • Второй параметр — это элемент конфигурации, который необходимо сбросить;
  • Третий параметр передается в функцию обратного вызова при изменении масштаба, и через функцию обратного вызова можно получить последнее значение масштаба;
  • Возвращает объект экземпляра, который предоставляет некоторые методы работы.
/**
 * scale 刻度函数
 * @param {String} el  html节点
 * @param {Object} options  配置信息
 * @param {Function} callBack 刻度变更回调函数
 * @returns { Object}
 */
// 绘制刻度尺
const myScale = scale.init('#myScale', {height: 50, start: 10000, end: 2000},callBack);
function callBack(value) {
  console.log(value);
}
Методы, в настоящее время предоставляемые возвращенным объектом экземпляра:
  • update(value): передать последнее значение масштаба, чтобы обновить отображение холста.value:最新刻度值
  • clear(): очистить текущий холст.
  • resize(option): чтобы сбросить холст, вы можете передать последнюю информацию о конфигурации, которую необходимо сбросить.option:刻度配置

myScale.update(1000); // 更新刻度值
myScale.clear();  // 清除画布
myScale.resize(); // 重置刻度画布

развивать

Детская обувь, кто не знаком с холстом апи, может посетитьВеб-сайт Canvas API на китайском языке Учить. Я также обобщил статьи о разработке Canvas и общих проблемах:

Вот объяснение идей развития, которые в основном разделены на следующие этапы:

  1. Элементы конфигурации и функции:
  2. Рисунок на холсте:
  3. провести осевую линию
  4. нарисовать всю шкалу
  5. Обрежьте шкалу в соответствии с фактическим значением шкалы.
  6. Нарисуйте подпись и фон
  7. Взаимодействие:
  8. Улучшите мониторинг скользящих событий и отрисуйте холст шкалы в режиме реального времени.
  9. Изменение числового значения и масштаба
  10. Различная обработка граничных исключений

элемент конфигурации

Элементы конфигурации, поддерживаемые компонентом, в настоящее время являются следующими конфигурациями:

// 默认配置
const default_conf = {
  // width: '',  // 不支持设置,取容器宽度
  height: 50, // 画布高度
  start: 1000, // 刻度开始值
  end: 10000, // 刻度结束值
  // def: 100, // 中心线停留位置 刻度值
  unit: 10, // 刻度间隔 'px'
  capacity: 100, // 刻度容量值
  background: '#fff', // 设置颜色则背景为对应颜色虚幻效果,不设置默认为全白。
  lineColor: '#087af7', // 中心线颜色
  openUnitChange: true, // 是否开启间隔刻度变更
  sign: '@nowThen', // 签名,传入空不显示签名
  fontColor: '#68ca68', // 刻度数值颜色, 刻度线颜色暂未提供设置
  fontSize: '16px SimSun, Songti SC', // 刻度数值 字体样式
};

пройти черезscale.init()Второй параметр входящего элемента конфигурации, в противном случае будет использоваться указанный выше элемент конфигурации по умолчанию.

рисунок на холсте

Нарисуйте холст на основе переданного контейнера:

  // 根据传入的容器绘制canvas
  const container = document.querySelector(el);
  container.appendChild(canvas);
  const canvas = document.createElement('canvas');
  const ctx = canvas.getContext('2d');

провести осевую линию

Средняя линия остается в середине холста и определяет текущее значение тика.

// 绘制中心线
function drawMidLine() {
  const mid_x = Math.floor(config.width / 2);
  ctx.beginPath();
  ctx.fillStyle = config.lineColor;
  ctx.fillRect(mid_x - 1, 0, 2, config.height);
  ctx.stroke();
  ctx.moveTo(mid_x, 8);
  ctx.lineTo(mid_x - 5, 2);
  ctx.lineTo(mid_x - 5, 0);
  ctx.lineTo(mid_x + 5, 0);
  ctx.lineTo(mid_x + 5, 2);
  ctx.fill();
  ctx.moveTo(mid_x, config.height - 8);
  ctx.lineTo(mid_x - 5, config.height - 2);
  ctx.lineTo(mid_x - 5, config.height);
  ctx.lineTo(mid_x + 5, config.height);
  ctx.lineTo(mid_x + 5, config.height - 2);
  ctx.fill();
  ctx.closePath();
}

Установить подпись и цвет фона

фоновый цветconfig.background: Фон является входящим значением цвета и дополнительно создает некоторые иллюзорные эффекты галочек с обеих сторон холста для холста. Если передается null, весь холст будет прозрачным, и эффект размытия с обоих концов не будет.

подписатьconfig.signОтображается в правом верхнем углу холста, если передано нулевое значение, подпись отображаться не будет.

  // 设置签名及背景
  function drawSign() {
    // 背景 虚化效果、、
    if (config.background) {
      ctx.beginPath();
      var gradient1 = ctx.createLinearGradient(0, 0, config.width, 0);
      gradient1.addColorStop(0, 'rgba(255, 255, 255, 0.95)');
      gradient1.addColorStop(0.45, 'rgba(255, 255, 255, 0)');
      gradient1.addColorStop(0.55, 'rgba(255, 255, 255, 0)');
      gradient1.addColorStop(1, 'rgba(255, 255, 255, 0.95)');
      ctx.fillStyle = gradient1;
      ctx.fillRect(0, 0, config.width, config.height);
      ctx.closePath();
    }
          
    // 签名
    if (config.sign) {
      ctx.beginPath();
      ctx.font = '10px Arial';
      var gradient = ctx.createLinearGradient(config.width, 0, config.width - 50, 0);
      gradient.addColorStop(0, 'rgba(255, 0, 0, 0.3)');
      gradient.addColorStop(1, 'rgba(0, 128, 0, 0.3)');
      ctx.fillStyle = gradient;
      ctx.textAlign = 'right';
      ctx.fillText(config.sign, config.width - 10, 10);
      ctx.closePath();
      ctx.fillStyle = 'transparent';
    }
  }

// 在绘制刻度尺时,设置背景值
ctx_bg.fillStyle = _config.background || 'transparent'; // 背景色

шкала рисования

Вот две возможные идеи:

  1. Все масштабное полотно отрисовывается заранее, а при скольжении часть масштабного полотна перехватывается и втягивается в видимую область согласно параметрам.
  2. В соответствии с текущим значением масштаба, расстоянием скольжения и другими параметрами в реальном времени рисуется распределение масштаба видимой области холста.

В реальной разработке были опробованы и реализованы оба варианта. Также выявил некоторые проблемы:

Первый вариант:

Шаги делятся на следующие:

  • По пришедшей конфигурации задаем стиль, рассчитываем ширину и высоту холста масштаба;
  • Нарисуйте нижнюю линию всей шкалы;
  • Подсчитайте количество масштабов и нарисуйте каждый масштаб по очереди, ширина масштаба 1 пиксель, нормальный масштаб1/5 * heightВысокий, каждые 5 тиков1/3 * heightВысокий, каждые 10 тиков1/2 * heighthigh и постройте тиковые значения;
  • Вычислить начальную позицию перехваченного масштабного холста;
  • затем пройтиcontext.getImageData(sx, sy, sWidth, sHeight);Перехватите область изображения шкалы наcontext.putImageData(imageData, 0, 0);Нарисуйте данные перехваченного выше объекта ImageData на основной холст.

использоватьcontext.drawImage()Такого же эффекта можно добиться.

При скольжении, в соответствии с положением скольжения, соответствующий график площади масштаба перехватывается и рисуется на основном холсте.

  // 创建新的刻度画布 作为底层图片
  const canvas_bg = document.createElement('canvas');
  const ctx_bg = canvas_bg.getContext('2d');

  // 绘制刻度尺
  function drawScale() {
    const mid = config.end - config.start + 1; // 取值范围

    const scale_len = Math.ceil(mid / config.capacity); // 刻度条数
    const space = Math.floor(config.width / 2); //左右两边间隙,根据该值计算整数倍刻度值画线
    const beginNum = Math.ceil(config.start / config.capacity) * config.capacity;
    const st = (Math.ceil(config.start / config.capacity) - config.start / config.capacity) * config.unit;

    // 设置canvas_bg宽高
    canvas_bg.width = (config.unit * (scale_len - 1) + config.width) * dpr;
    canvas_bg.height = config.height * dpr;
    ctx_bg.scale(dpr, dpr);

    ctx_bg.beginPath();
    ctx_bg.fillStyle = config.background || 'transparent'; // 背景色
    ctx_bg.fillRect(0, 0, canvas_bg.width, config.height);
    ctx_bg.closePath();
    // 底线
    ctx_bg.beginPath();
    ctx_bg.moveTo(0, config.height);
    ctx_bg.lineTo(canvas_bg.width, config.height);
    ctx_bg.strokeStyle = config.scaleLineColor || '#9E9E9E';
    ctx_bg.lineWidth = 1;
    ctx_bg.stroke();
    ctx_bg.closePath();

    // 绘制刻度线
    for (let i = 0; i < scale_len; i++) {
      ctx_bg.beginPath();
      ctx_bg.strokeStyle = config.scaleLineColor || "#9E9E9E";
      ctx_bg.font = config.fontSize;
      ctx_bg.fillStyle = config.fontColor;
      ctx_bg.textAlign = 'center';
      ctx_bg.shadowBlur = 0;

      const curPoint = i * config.unit + space + st;
      const curNum = i * config.capacity + beginNum;
      if (curNum % (config.capacity * 10) === 0) {
        ctx_bg.moveTo(curPoint, (config.height * 1) / 2);
        ctx_bg.strokeStyle = config.scaleLineColor || "#666";
        ctx_bg.shadowColor = '#9e9e9e';
        ctx_bg.shadowBlur = 1;
        ctx_bg.fillText(
          curNum,
          curPoint,
          (config.height * 1) / 3
        );
      } else if (curNum % (config.capacity * 5) === 0) {
        ctx_bg.moveTo(curPoint, (config.height * 2) / 3);
        ctx_bg.strokeStyle = config.scaleLineColor || "#888";
        if (scale_len <= 10) {
          ctx_bg.font = '12px Helvetica, Tahoma, Arial';
          ctx_bg.fillText(
            curNum,
            curPoint,
            (config.height * 1) / 2
          );
        }
      } else {
        ctx_bg.moveTo(curPoint, (config.height * 4) / 5);
        if (i === 0 || i === scale_len - 1) {
          ctx_bg.font = '12px Helvetica, Tahoma, Arial';
          ctx_bg.fillText(
            curNum,
            curPoint,
            (config.height * 2) / 3
          );
        }
      }
      ctx_bg.lineTo(curPoint, config.height);
      ctx_bg.stroke();
      ctx_bg.closePath();
    }

    point_x = (config.def - config.start) / config.capacity * config.unit; //初始化开始位置
    const imageData = ctx_bg.getImageData(point_x * dpr, 0, config.width * dpr, config.height * dpr)
    ctx.putImageData(imageData, 0, 0);
  }
Второй вариант:

Шаги делятся на следующие:

  • Нарисуйте нижнюю линию всей шкалы;
  • Основываясь на центральной шкале, вычислите крайнее левое значение шкалы;
  • Вычислите количество делений, которые можно нарисовать в области холста, а также положение и значение масштаба первого деления;
  • Нарисуйте каждую галочку по очереди, ширина галочки 1px, обычная галочка1/5 * heightВысокий, каждые 5 тиков1/3 * heightВысокий, каждые 10 тиков1/2 * heighthigh и постройте тиковые значения;
  • потомcontext.drawImage()нарисуйте изображение на основной области холста;

При скольжении текущее значение промежуточного масштаба вычисляется в реальном времени, и весь холст перерисовывается в соответствии с приведенными выше шагами рисования.

  // 创建新的刻度画布 作为底层图片
  const canvas_bg = document.createElement('canvas');
  const ctx_bg = canvas_bg.getContext('2d');

  // 绘制刻度尺
  function drawScale() {
    // 设置canvas_bg宽高
    canvas_bg.width = (config.unit * (scale_len - 1) + config.width) * dpr;
    canvas_bg.height = config.height * dpr;
    ctx_bg.scale(dpr, dpr);

    // 以中点刻度为基准,获取最左侧刻度值
    let begin_num = current_def - (config.width / 2) * (config.capacity / config.unit);
    let cur_x = 0;
    let cur_num = 0;
    const scale_len = Math.ceil((config.width + 1) / config.unit); // 刻度条数
    const real_len = Math.ceil((config.end - config.start + 1) / config.capacity); // 实际可绘制的刻度条数

    ctx_bg.fillStyle = config.background || 'transparent'; // 背景色
    ctx_bg.fillRect(0, 0, config.width, config.height);
    ctx_bg.closePath();
    // 底线
    ctx_bg.beginPath();
    ctx_bg.moveTo(0, config.height);
    ctx_bg.lineTo(config.width, config.height);
    ctx_bg.strokeStyle = config.scaleLineColor || '#9E9E9E';
    ctx_bg.lineWidth = 1;
    ctx_bg.stroke();
    ctx_bg.closePath();

    let space_num = Math.ceil(begin_num / config.capacity) * config.capacity - begin_num;
    let space_x = space_num * (config.unit / config.capacity);

    // 绘制刻度线
    for (let i = 0; i < scale_len; i++) {
      cur_num = (Math.ceil(begin_num / config.capacity) + i) * config.capacity;
      if (cur_num < config.start) {
        continue;
      } else if (cur_num > config.end) {
        break;
      }

      ctx_bg.beginPath();
      ctx_bg.strokeStyle = config.scaleLineColor || "#9E9E9E";
      ctx_bg.font = config.fontSize;
      ctx_bg.fillStyle = config.fontColor;
      ctx_bg.textAlign = 'center';
      ctx_bg.shadowBlur = 0;
      cur_x = space_x + i * config.unit;

      if (cur_num % (config.capacity * 10) === 0) {
        ctx_bg.moveTo(cur_x, (config.height * 1) / 2);
        ctx_bg.strokeStyle = config.scaleLineColor || "#666";
        ctx_bg.shadowColor = '#9e9e9e';
        ctx_bg.shadowBlur = 1;
        ctx_bg.fillText(
          cur_num,
          cur_x,
          (config.height * 1) / 3
        );
      } else if (cur_num % (config.capacity * 5) === 0) {
        ctx_bg.moveTo(cur_x, (config.height * 2) / 3);
        ctx_bg.strokeStyle = config.scaleLineColor || "#888";
        if (real_len <= 10) {
          ctx_bg.font = '12px Helvetica, Tahoma, Arial';
          ctx_bg.fillText(
            cur_num,
            cur_x,
            (config.height * 1) / 2
          );
        }
      } else {
        ctx_bg.moveTo(cur_x, (config.height * 4) / 5);
      }
      ctx_bg.lineTo(cur_x, config.height);
      ctx_bg.stroke();
      ctx_bg.closePath();
    }
    ctx.drawImage(canvas_bg, 0, 0, config.width * dpr, config.height * dpr, 0, 0, config.width, config.height);  
  }
Суммировать

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

Таким образом создается первоначальный масштабный стиль.

image.png
Следующим шагом является добавление эффекта скользящего взаимодействия.

Проблемы, возникающие при разработке(более подробная инструкция далее в статье):

  • Мобильный рисунок холста размыт, а изображение вставки холста размыто;
  • проблема с параметром context.drawImage();
  • Когда размер отрисовки холста или размер вложенного изображения больше определенного порога, может возникнуть проблема с пустым отрисовкой.

Скользящий мониторинг событий, совместимый с ПК

Слушайте события скольжения влево и вправо, определяйте расстояние скольжения каждого пальца, вычисляйте расстояние, на которое должна сместиться шкала, и перерисовывайте холст.

  1. Событие регистрации совместимо с мобильным терминалом и ПК-терминалом, мобильный терминал прослушивает событие касания, а ПК-терминал прослушивает событие мыши.Здесь следует обратить внимание на разницу в обработке получения текущей точки касания.

Мобильный терминал получает координату X текущей точки касания:e.touches[0].pageX;Компьютерная сторона получает координату X мыши:e.pageX.

  1. В событии перемещения рисуйте в реальном времени, обновляйте значение масштаба в реальном времени и вызывайте входящую функцию обратного вызова, чтобы вернуть значение масштаба;
  2. Обрабатывать случай перемещения к левой и правой границам, при достижении установленных максимального и минимального значений он не может продолжать движение;
  3. использоватьwindow.requestAnimationFrame()Оптимизируйте частоту рендеринга.
  4. Перед перекраской необходимо использоватьctx.clearRect()Очистите холст перед рисованием, иначе рисунки будут перекрываться.
  5. По элементу комплектацииconfig.openUnitChangeОпределяет, можно ли перемещать только тики интервала, например, 100, 200, 300... изменения. входящийfalseизменяется в соответствии с фактическим расстоянием перемещения.

Эффект следующий:

按刻度移动.gif
非刻度移动.gif
Основной код:

  // 事件交互 (第一种方案)
  function addEvent() {
    let begin_x = 0; // 手指x坐标
    let ifMove = false; // 是否开始交互
    let moveDistance = 0;

    // 注册事件,移动端和PC端
    const hasTouch = 'ontouchstart' in window;
    const startEvent = hasTouch ? 'touchstart' : 'mousedown';
    const moveEvent = hasTouch ? 'touchmove' : 'mousemove';
    const endEvent = hasTouch ? 'touchend' : 'mouseup';
    canvas.addEventListener(startEvent, start);
    canvas.addEventListener(moveEvent, move);
    canvas.addEventListener(endEvent, end);

    function start(e) {
      e.stopPropagation();
      e.preventDefault();
      ifMove = true;
      if (!e.touches) {
        begin_x = e.pageX;
      } else {
        begin_x = e.touches[0].pageX;
      }
    }

    function move(e) {
      e.stopPropagation();
      e.preventDefault();
      const current_x = e.touches ? e.touches[0].pageX : e.pageX;
      if (ifMove) {
        moveDistance = current_x - begin_x;
        begin_x = current_x;
        point_x = point_x - moveDistance; //刻度偏移量
        const space = Math.floor(config.width / 2);
        // 边界值处理
        if (point_x <= 0) {
          point_x = 0;
        } else if (point_x >= canvas_bg.width / dpr - config.width) {
          point_x = canvas_bg.width / dpr - config.width;
        }

        window.requestAnimationFrame(moveDraw)
      }
    }

    function end(e) {
      ifMove = false;
    }
  }

  function moveDraw() {
    let now_x = point_x;
    // 是否刻度移动
    if (config.openUnitChange) {
      const st = ( config.start / config.capacity - Math.floor(config.start / config.capacity)) * config.unit;
      now_x = Math.round(this.point_x / config.unit) * config.unit - st;
    }
    ctx.clearRect(0, 0, config.width, config.height);
    // ctx.drawImage(canvas_bg, now * dpr, 0, config.width * dpr, config.height * dpr, 0, 0, config.width, config.height);
    var imageData = ctx_bg.getImageData(now_x * dpr, 0, config.width * dpr, config.height * dpr)
    ctx.putImageData(imageData, 0, 0)
    drawMidLine();
    drawSign();
    const value = now_x * config.capacity / config.unit + config.start;
    if (typeof callBack === 'function') {
      callBack(Math.round(value));
    } else {
      throw new Error('scale函数的第二个参数,必须为正确的回调函数!')
    }
  }

Плавное движение, функция смягчения

Приведенное выше взаимодействие событий — это код для первого сценария. Плавное смягчение не делается. Исходя из этого, изменен код второй схемы, добавлено плавное движение, функция easing принимает easyOut, которая сначала быстрая, а потом медленная.

// easeOut 缓动函数
const slowActionfn = function (t, b, c, d) {
  return c * ((t = t / d - 1) * t * t + 1) + b;
};  

// 事件交互
function addEvent() {
  let begin_x = 0; // 手指x坐标
  let last_x = 0; //上一次x坐标
  let ifMove = false; // 是否开始交互
  let from_def = 0;
  let lastMoveTime = 0;
  let lastMove_x = 0;

  // 注册事件,移动端和PC端
  const hasTouch = 'ontouchstart' in window;
  const startEvent = hasTouch ? 'touchstart' : 'mousedown';
  const moveEvent = hasTouch ? 'touchmove' : 'mousemove';
  const endEvent = hasTouch ? 'touchend' : 'mouseup';
  canvas.addEventListener(startEvent, start);
  canvas.addEventListener(moveEvent, move);
  canvas.addEventListener(endEvent, end);

  function start(e) {
    e.stopPropagation();
    e.preventDefault();
    ifMove = true;
    if (!e.touches) {
      last_x = begin_x = e.pageX;
    } else {
      last_x = begin_x = e.touches[0].pageX;
    }
    lastMove_x = last_x;
    lastMoveTime = e.timeStamp || Date.now();
  }

  function move(e) {
    e.stopPropagation();
    e.preventDefault();
    const current_x = e.touches ? e.touches[0].pageX : e.pageX;
    if (ifMove) {
      move_x = current_x - last_x;
      current_def = current_def - move_x * (config.capacity / config.unit);
      window.requestAnimationFrame(moveDraw);
      last_x = current_x;

      const nowTime = e.timeStamp || Date.now();
      if (nowTime - lastMoveTime > 300) {
        lastMoveTime = nowTime;
        lastMove_x = last_x;
      }
    }
  }


  function end(e) {
    const current_x = e.changedTouches ? e.changedTouches[0].pageX : e.pageX;
    const nowTime = e.timeStamp || Date.now();
    const v = -(current_x - lastMove_x) / (nowTime - lastMoveTime); //最后一段时间手指划动速度

    ifMove = false;
    let t = 0, d = 15;
    if (Math.abs(v) >= 0.3) {
      from_def = current_def;
      step();
    } else {
      if (current_def < config.start) {
        current_def = config.start;
      } else if (current_def > config.end) {
        current_def = config.end;
      }
      if (config.openUnitChange) {
        current_def = Math.round(current_def / config.capacity) * config.capacity;
      }
      moveDraw();
    }

    function step() {
      current_def = slowActionfn(t, from_def, (config.capacity) * v * 50, d);
      if (current_def < config.start) {
        current_def = config.start;
      } else if (current_def > config.end) {
        current_def = config.end;
      }
      if (config.openUnitChange) {
        current_def = Math.round(current_def / config.capacity) * config.capacity;
      }
      moveDraw()
      t++;
      if (t <= d) {
        // 继续运动
        window.requestAnimationFrame(step);
      } else {
        // 结束
      }
    }
  }
}

function moveDraw() {
  ctx.clearRect(0, 0, config.width, config.height);

  drawScale();
  drawMidLine();
  drawSign();

  if (typeof callBack === 'function') {
    callBack(Math.round(current_def));
  } else {
    throw new Error('scale函数的第二个参数,必须为正确的回调函数!')
  }
}

Граничная обработка

В процессе рисования также необходимо обрабатывать различные крайние случаи:

  1. Сделайте простой запрос проверки для входящего элемента конфигурации, например, является ли идентификатор действительным узлом dom;
  2. Рассчитайте и установите положение центральной линии по умолчанию;
  3. При скольжении обрабатывается пороговое значение при скольжении в крайнее левое и крайнее правое положение;
  4. Обработка, когда входящее значение шкалы не находится в интервале шкалы;

Эти крайние случаи обрабатываются просто в коде библиотеки компонентов.

Общая проблема

Подробнее читайте в другой моей статье - "Запишите использование холста и распространенные проблемы"

Размытая проблема рисования холста на мобильном телефоне

Феномен

Как показано ниже:

image.png
На приведенном выше рисунке, когда обработка совместимого мобильного терминала не выполняется, нарисованный компонент масштаба холста находится на модели iPhone 6s, а графика и текст холста размыты и искажены. Качество графики гарантируется после совместимой обработки. Вышеупомянутые компоненты шкалы относятся к моей другой статье, адрес:у-у-у-у. yuque.com/теперь тогда/ ты…

причина

Что касается таких проблем, как DPR экрана высокой четкости на мобильном терминале, размытые изображения и адаптация мобильного терминала, если вы не уверены в детской обуви, см. "Что нужно знать о мобильной адаптации"Эта статья будет более подробной. Я не буду вдаваться в подробности, в этой статье речь пойдет только о размытии Canvas на мобильных устройствах. На экране высокого разрешения мобильного терминала часто встречается проблема размытия графики Canvas. По сути, это то же самое, что и проблема размытия мобильных изображений. Изображение, нарисованное холстом, также является растровым.dpr > 1На экране растрового изображения пиксель растрового изображения может отображаться несколькими физическими пикселями, но этим физическим пикселям нельзя точно присвоить цвет соответствующего пикселя растрового изображения, а только приблизительноdpr > 1будет размыто на экране. При рисовании холста на стороне ПК мы все напрямую обрабатываем пиксели css, где 1 пиксель холста напрямую равен 1 пикселю. Это не проблема. В настоящее время на экране ПК он должен быть равен 1. пока вdpr > 1Это невозможно сделать прямо на экране мобильного устройства .

решить

Решение, конечно же, начинается с dpr.

  1. пройти черезwindow.devicePixelRatioПолучить dpr текущего экрана устройства;
  2. Сначала получите или установите ширину и высоту контейнера Canvas;
  3. В соответствии с dpr установите свойства ширины и высоты элемента холста; вdpr = 2Это эквивалентно двукратному расширению холста;
  4. пройти черезcontext.scale(dpr, dpr)Масштабируйте систему координат холста Canvas. существуетdpr = 2Это эквивалентно расширению системы координат холста в два раза, так что коэффициент прорисовки увеличивается в два раза, а затем фактические пиксели рисования холста могут быть обработаны в соответствии с исходными значениями пикселей.

При отображении на экране увеличенная графика холста масштабируется и отображается в контейнере холста. Чтобы обеспечить качество графики на холсте.

// 获取dpr
const dpr = window.devicePixelRatio; 
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// 获取Canvas容器的宽高
const { width: cssWidth, height: cssHeight } = canvas.getBoundingClientRect();
// 根据dpr,设置Canvas的宽高,使1个canvas像素和1个物理像素相等
canvas.width = dpr * cssWidth;
canvas.height = dpr * cssHeight;
// 根据dpr,设置canvas元素的宽高属性
ctx.scale(dpr,dpr);

Проблема с параметром Canvas drawImage(), проблема с размытием мобильного изображения

Особенно сбивает с толку функция холста drawImage(). Позиции параметров для его 5-параметрического и 9-параметрического использования различны. Если вы не заметите этого в реальной разработке, вы будете особенно сбиты с толку тем, в чем проблема! пот!

Метод DrawImage()Есть очень странное место, на которое все должны обратить внимание, то есть позиции параметров 5-параметрического и 9-параметрического использования отличаются, что отличается от общего API. Дополнительные параметры общего API размещены сзади. Однако, когда drawImage() использует здесь 9 параметров, необязательные параметры sx, sy, sWidth и sHeight находятся впереди. Если не обращать на это внимания, некоторые выступления будут выше вашего понимания.

И графика, вставленная функцией drawImage(), находится на мобильной стороне.dpr >1Экран также имеет проблему смазанных изображений. При загрузке другого нарисованного элемента Canvas через drawImage() на мобильной стороне следует также обратить внимание на совместимость другого элемента canvas, а также обратить внимание на разницу между двумя системами координат.

// 设置canvas_bg宽高
canvas_bg.width = (config.unit * (scale_len - 1) + config.width) * dpr;
canvas_bg.height = config.height * dpr;
ctx_bg.scale(dpr, dpr);

...

// 初始化开始位置
point_x = (config.def - config.start) / config.capacity * config.unit;
//在主画布ctx上,通过drawImage()插入另一个canvas_bg画布;
ctx.drawImage(canvas_bg, point_x * dpr, 0, config.width * dpr, config.height * dpr, 0, 0, config.width, config.height);

В приведенном выше коде холст canvas_bg также должен иметь дело с проблемой размытия холста, упомянутой выше; на основном холсте ctx, при вставке другого изображения холста canvas_bg через drawImage(), вам нужно обратить внимание на разницу в соотношении две системы координат в настоящее время Система координат canvas_bg масштабируется в соответствии с dpr.

Когда размер рисунка холста или вставка изображений drawImage, получение графических ресурсов getImageDate и другие размеры превышают определенный порог, может возникнуть проблема с пустым рисунком.

В реальной разработке, когда размер рисунка холста или drawImage, вставляющего изображение, getImageDate, получающего графические ресурсы и т. д., превышает определенный порог, все визуализируемое изображение остается пустым. Этот конкретный порог не определен и связан с операционной средой. Но это также должно быть скрытой опасностью, которую drawImage рисует, не зная когда. Например, на рисунке ниже масштаб нарисованного холста слишком большой, после его перехвата и рендеринга на основной холст весь масштаб пустой, но на взаимодействие это не влияет.

image.png

Пакет библиотеки компонентов

Выше приведена идея разработки и основной код. Затем его необходимо упаковать в файл библиотеки компонентов с открытым исходным кодом, который можно напрямую ввести в проект для использования.
Во-первых, правильно преобразуйте код, инкапсулируйте его в класс и создайте код, манипулируя экземпляром. Чтобы улучшить повторное использование кода и избежать взаимодействия нескольких вводов и применений.
Окончательная структура кода:

image.png
Перейдите к проекту, чтобы просмотреть конкретный код.

Для простоты создайте библиотеку компонентов напрямую, используя режим сборки библиотеки, предоставляемый vue-cli3. Встроенная библиотека будет выводить версии пакетов CommonJS, пакетов UMD и т. д. Получите соответствующий файл при его использовании.

image.png

canvas-scaleБиблиотека компонентов опубликована на npm. адрес нпм:Уууу, эта лошадь plus.com/package/miserable…

Ссылка на проект:

исходный адрес гитхаба:GitHub.com/now1 слишком безжалостен/руб…;
Статья - Юкэ:у-у-у-у. yuque.com/теперь тогда/ ты…;

Очередная статья - "Запишите использование холста и распространенные проблемы"

Адрес демонстрации компонента:rnlvwyx.cn:3333/#/demo;(Должна быть добавлена ​​демонстрация задействованных элементов конфигурации...)

Любые вопросы приветствуются для обсуждения...