предисловие
На этот раз я делюсь компонентом настраиваемого масштаба (линейки), нарисованным мной с помощью Canvas. Компонентное приложение: в основном используется для скользящего выбора значений мобильного терминала, сумм и т. д., чтобы улучшить взаимодействие с пользователем.
Статья предполагает знание идей разработки, дизассемблирования кода, решения проблем, инкапсуляции библиотеки компонентов масштабирования и т. д. Детская обувь, нуждающаяся в помощи, может быть правильно названа для разработки более блестящих и совершенных компонентов.
Добро пожаловать на адрес github, начните много:GitHub.com/now1 слишком безжалостен/руб…;
Также приглашаем вас посетить функцию демо-адреса:rnlvwyx.cn:3333/#/demo;
Описание функционального анализа
Демонстрация масштабируемого компонента, рендеринг:
Из анализа диаграммы эффекта видно, что центральная линия не перемещается посередине; отображается эффект длины шкалы интервала и значение шкалы шкалы каждые 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 и общих проблемах:
Вот объяснение идей развития, которые в основном разделены на следующие этапы:
- Элементы конфигурации и функции:
- Рисунок на холсте:
- провести осевую линию
- нарисовать всю шкалу
- Обрежьте шкалу в соответствии с фактическим значением шкалы.
- Нарисуйте подпись и фон
- Взаимодействие:
- Улучшите мониторинг скользящих событий и отрисуйте холст шкалы в режиме реального времени.
- Изменение числового значения и масштаба
- Различная обработка граничных исключений
элемент конфигурации
Элементы конфигурации, поддерживаемые компонентом, в настоящее время являются следующими конфигурациями:
// 默认配置
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 пиксель, нормальный масштаб
1/5 * height
Высокий, каждые 5 тиков1/3 * height
Высокий, каждые 10 тиков1/2 * height
high и постройте тиковые значения; - Вычислить начальную позицию перехваченного масштабного холста;
- затем пройти
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 * height
high и постройте тиковые значения; - потом
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);
}
Суммировать
Первый вариант:Он проще в реализации; этот масштабный холст нужно нарисовать только один раз, и нет необходимости перерисовывать масштабный холст при скольжении; он также может более интуитивно отражать движение масштаба, Однако, когда диапазон интервала масштаба чертежа велик, производительность неудовлетворительна, а размер холста слишком велик, и возникает проблема рисования заготовок.Второй вариант:Трудно найти начальное значение и конечное значение масштаба видимой области, а при его скольжении весь холст пересчитывает каждую точку рисования. На первый взгляд, реализация более хлопотная, и весь холст приходится рисовать в реальном времени при скольжении, но по сравнению с фатальным недостатком первого решения эффект, производительность и совместимость лучше.
Таким образом создается первоначальный масштабный стиль.
Следующим шагом является добавление эффекта скользящего взаимодействия.Проблемы, возникающие при разработке(более подробная инструкция далее в статье):
- Мобильный рисунок холста размыт, а изображение вставки холста размыто;
- проблема с параметром context.drawImage();
- Когда размер отрисовки холста или размер вложенного изображения больше определенного порога, может возникнуть проблема с пустым отрисовкой.
Скользящий мониторинг событий, совместимый с ПК
Слушайте события скольжения влево и вправо, определяйте расстояние скольжения каждого пальца, вычисляйте расстояние, на которое должна сместиться шкала, и перерисовывайте холст.
- Событие регистрации совместимо с мобильным терминалом и ПК-терминалом, мобильный терминал прослушивает событие касания, а ПК-терминал прослушивает событие мыши.Здесь следует обратить внимание на разницу в обработке получения текущей точки касания.
Мобильный терминал получает координату X текущей точки касания:e.touches[0].pageX
;Компьютерная сторона получает координату X мыши:e.pageX
.
- В событии перемещения рисуйте в реальном времени, обновляйте значение масштаба в реальном времени и вызывайте входящую функцию обратного вызова, чтобы вернуть значение масштаба;
- Обрабатывать случай перемещения к левой и правой границам, при достижении установленных максимального и минимального значений он не может продолжать движение;
- использовать
window.requestAnimationFrame()
Оптимизируйте частоту рендеринга. - Перед перекраской необходимо использовать
ctx.clearRect()
Очистите холст перед рисованием, иначе рисунки будут перекрываться. - По элементу комплектации
config.openUnitChange
Определяет, можно ли перемещать только тики интервала, например, 100, 200, 300... изменения. входящийfalse
изменяется в соответствии с фактическим расстоянием перемещения.
Эффект следующий:
Основной код: // 事件交互 (第一种方案)
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函数的第二个参数,必须为正确的回调函数!')
}
}
Граничная обработка
В процессе рисования также необходимо обрабатывать различные крайние случаи:
- Сделайте простой запрос проверки для входящего элемента конфигурации, например, является ли идентификатор действительным узлом dom;
- Рассчитайте и установите положение центральной линии по умолчанию;
- При скольжении обрабатывается пороговое значение при скольжении в крайнее левое и крайнее правое положение;
- Обработка, когда входящее значение шкалы не находится в интервале шкалы;
Эти крайние случаи обрабатываются просто в коде библиотеки компонентов.
Общая проблема
Подробнее читайте в другой моей статье - "Запишите использование холста и распространенные проблемы"
Размытая проблема рисования холста на мобильном телефоне
Феномен
Как показано ниже:
На приведенном выше рисунке, когда обработка совместимого мобильного терминала не выполняется, нарисованный компонент масштаба холста находится на модели iPhone 6s, а графика и текст холста размыты и искажены. Качество графики гарантируется после совместимой обработки. Вышеупомянутые компоненты шкалы относятся к моей другой статье, адрес:у-у-у-у. yuque.com/теперь тогда/ ты…причина
Что касается таких проблем, как DPR экрана высокой четкости на мобильном терминале, размытые изображения и адаптация мобильного терминала, если вы не уверены в детской обуви, см. "Что нужно знать о мобильной адаптации"Эта статья будет более подробной. Я не буду вдаваться в подробности, в этой статье речь пойдет только о размытии Canvas на мобильных устройствах.
На экране высокого разрешения мобильного терминала часто встречается проблема размытия графики Canvas. По сути, это то же самое, что и проблема размытия мобильных изображений. Изображение, нарисованное холстом, также является растровым.dpr > 1
На экране растрового изображения пиксель растрового изображения может отображаться несколькими физическими пикселями, но этим физическим пикселям нельзя точно присвоить цвет соответствующего пикселя растрового изображения, а только приблизительноdpr > 1
будет размыто на экране.
При рисовании холста на стороне ПК мы все напрямую обрабатываем пиксели css, где 1 пиксель холста напрямую равен 1 пикселю. Это не проблема. В настоящее время на экране ПК он должен быть равен 1. пока вdpr > 1
Это невозможно сделать прямо на экране мобильного устройства .
решить
Решение, конечно же, начинается с dpr.
- пройти через
window.devicePixelRatio
Получить dpr текущего экрана устройства; - Сначала получите или установите ширину и высоту контейнера Canvas;
- В соответствии с dpr установите свойства ширины и высоты элемента холста; в
dpr = 2
Это эквивалентно двукратному расширению холста; -
пройти через
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 рисует, не зная когда. Например, на рисунке ниже масштаб нарисованного холста слишком большой, после его перехвата и рендеринга на основной холст весь масштаб пустой, но на взаимодействие это не влияет.
Пакет библиотеки компонентов
Выше приведена идея разработки и основной код. Затем его необходимо упаковать в файл библиотеки компонентов с открытым исходным кодом, который можно напрямую ввести в проект для использования.
Во-первых, правильно преобразуйте код, инкапсулируйте его в класс и создайте код, манипулируя экземпляром. Чтобы улучшить повторное использование кода и избежать взаимодействия нескольких вводов и применений.
Окончательная структура кода:
Для простоты создайте библиотеку компонентов напрямую, используя режим сборки библиотеки, предоставляемый vue-cli3. Встроенная библиотека будет выводить версии пакетов CommonJS, пакетов UMD и т. д. Получите соответствующий файл при его использовании.
canvas-scale
Библиотека компонентов опубликована на npm. адрес нпм:Уууу, эта лошадь plus.com/package/miserable…
Ссылка на проект:
исходный адрес гитхаба:GitHub.com/now1 слишком безжалостен/руб…;
Статья - Юкэ:у-у-у-у. yuque.com/теперь тогда/ ты…;
Очередная статья - "Запишите использование холста и распространенные проблемы"
Адрес демонстрации компонента:rnlvwyx.cn:3333/#/demo;(Должна быть добавлена демонстрация задействованных элементов конфигурации...)
Любые вопросы приветствуются для обсуждения...