slide-ruler
Version:
SlideRuler Component 滑尺数值选择器
272 lines (253 loc) • 7.81 kB
JavaScript
/*
* @Desc: slide ruler
* @Author: simbawu
* @Date: 2019-04-16 20:15:13
* @LastEditors: simbawu
* @LastEditTime: 2019-08-07 14:00:00
*/
import s from './slide-ruler.scss';
class sliderRuler {
constructor(options = {}) {
this.value = '';
this.options = {
canvasWidth: document.body.clientWidth || 375,
canvasHeight: 83,
boxColor: '#E4E4E4',
scrollLeft: 0,
heightDecimal: 35,
heightDigit: 18,
lineWidth: 2,
colorDecimal: '#E4E4E4',
colorDigit: '#E4E4E4',
divide: 10,
precision: 1,
fontSize: 20,
fontColor: '#666',
fontMarginTop: 35,
maxValue: 230,
minValue: 100,
currentValue: 160
};
this.localState = {
startX: 0,
startY: 0,
isTouchEnd: true,
touchPoints: []
};
this.browserEnv = window.hasOwnProperty('ontouchstart');
this.options = { ...this.options, ...options };
this.init(this.options);
}
_renderBox(container) {
const box = document.createElement('div'),
canvas = document.createElement('canvas');
this.canvas = canvas;
box.className = s.box;
box.appendChild(canvas);
container.appendChild(box);
this._renderCanvas();
}
_renderCanvas() {
const { canvasWidth, canvasHeight } = this.options,
canvas = this.canvas;
canvas.width = canvasWidth * 2;
canvas.height = canvasHeight * 2;
canvas.style.width = canvasWidth + 'px';
canvas.style.height = canvasHeight + 'px';
canvas.className = s.canvas;
if (this.browserEnv) {
canvas.ontouchstart = this.touchStart.bind(this);
canvas.ontouchmove = this.touchMove.bind(this);
canvas.ontouchend = this.touchEnd.bind(this);
} else {
canvas.onmousedown = this.touchStart.bind(this);
canvas.onmousemove = this.touchMove.bind(this);
canvas.onmouseup = this.touchEnd.bind(this);
}
this.dreawCanvas();
}
touchStart(e) {
e.preventDefault();
if (e || this.localState.isTouchEnd) {
this.touchPoints(e);
let touch = (e.touches && e.touches[0]) || e;
this.localState.startX = touch.pageX;
this.localState.startY = touch.pageY;
this.localState.startT = new Date().getTime(); // 记录手指按下的开始时间
this.localState.isTouchEnd = false; // 当前开始滑动
}
}
touchMove(e) {
if (!this.browserEnv && (e.which !== 1 || e.buttons === 0)) return; // pc canvas超出边界处理
this.touchPoints(e);
let touch = (e.touches && e.touches[0]) || e,
deltaX = touch.pageX - this.localState.startX,
deltaY = touch.pageY - this.localState.startY;
// 如果X方向上的位移大于Y方向,则认为是左右滑动
if (
Math.abs(deltaX) > Math.abs(deltaY) &&
Math.abs(Math.round(deltaX / this.options.divide)) > 0
) {
if (this.browserEnv && !this.rebound(deltaX)) {
return;
}
this.moveDreaw(deltaX);
this.localState.startX = touch.pageX;
this.localState.startY = touch.pageY;
}
}
touchEnd() {
this.moveDreaw(this.browserEnv ? this.inertialShift() : 0);
this.localState.isTouchEnd = true;
this.localState.touchPoints = [];
this.canvas.style.transform = 'translate3d(0, 0, 0)';
}
touchPoints(e) {
let touch = (e.touches && e.touches[0]) || e,
time = new Date().getTime(),
shift = touch.pageX;
this.localState.touchPoints.push({ time: time, shift: shift });
}
inertialShift() {
let s = 0;
if (this.localState.touchPoints.length >= 4) {
let _points = this.localState.touchPoints.slice(-4),
v =
((_points[3].shift - _points[0].shift) /
(_points[3].time - _points[0].time)) *
1000; // v 手指离开屏幕后的速度px/s
const a = 6000; // a 手指离开屏幕后的加速度
s = (Math.sign(v) * Math.pow(v, 2)) / (2 * a); // s 手指离开屏幕后惯性距离
}
return s;
}
rebound(deltaX) {
const { currentValue, maxValue, minValue } = this.options;
if (
(currentValue === minValue && deltaX > 0) ||
(currentValue === maxValue && deltaX < 0)
) {
let reboundX =
Math.sign(deltaX) * 1.5988 * Math.pow(Math.abs(deltaX), 0.7962);
this.canvas.style.transform = `translate3d(${reboundX}px, 0, 0)`;
return false;
}
return true;
}
moveDreaw(shift) {
const { divide, precision } = this.options;
let moveValue = Math.round(-shift / divide),
_moveValue = Math.abs(moveValue),
draw = () => {
if (_moveValue < 1) {
return;
}
this.options.currentValue += Math.sign(moveValue) * precision;
this.dreawCanvas();
window.requestAnimationFrame(draw);
_moveValue--;
};
draw();
}
dreawCanvas() {
const canvas = this.canvas,
context = canvas.getContext('2d');
canvas.height = canvas.height;
let {
canvasWidth,
canvasHeight,
maxValue,
minValue,
currentValue,
handleValue,
precision,
divide,
heightDecimal,
heightDigit,
lineWidth,
colorDecimal,
colorDigit,
fontSize,
fontColor,
fontMarginTop
} = this.options;
// 计算当前值
currentValue =
currentValue > minValue
? currentValue < maxValue
? currentValue
: maxValue
: minValue;
currentValue =
(Math.round((currentValue * 10) / precision) * precision) / 10;
this.options.currentValue = currentValue;
handleValue && handleValue(currentValue);
let diffCurrentMin = ((currentValue - minValue) * divide) / precision,
startValue =
currentValue - Math.floor(canvasWidth / 2 / divide) * precision;
startValue =
startValue > minValue
? startValue < maxValue
? startValue
: maxValue
: minValue;
let endValue = startValue + (canvasWidth / divide) * precision;
endValue = endValue < maxValue ? endValue : maxValue;
// 定义原点
let origin = {
x:
diffCurrentMin > canvasWidth / 2
? (canvasWidth / 2 -
((currentValue - startValue) * divide) / precision) *
2
: (canvasWidth / 2 - diffCurrentMin) * 2,
y: canvasHeight * 2
};
// 定义刻度线样式
heightDecimal = heightDecimal * 2;
heightDigit = heightDigit * 2;
lineWidth = lineWidth * 2;
// 定义刻度字体样式
fontSize = fontSize * 2;
fontMarginTop = fontMarginTop * 2;
// 每个刻度所占位的px
divide = divide * 2;
// 定义每个刻度的精度
const derivative = 1 / precision;
for (
let i = Math.round((startValue / precision) * 10) / 10;
i <= endValue / precision;
i++
) {
context.beginPath();
// 画刻度线
context.moveTo(origin.x + (i - startValue / precision) * divide, 0);
// 画线到刻度高度,10的位数就加高
context.lineTo(
origin.x + (i - startValue / precision) * divide,
i % 10 === 0 ? heightDecimal : heightDigit
);
context.lineWidth = lineWidth;
// 10的位数就加深
context.strokeStyle = i % 10 === 0 ? colorDecimal : colorDigit;
context.stroke();
// 描绘刻度值
context.fillStyle = fontColor;
context.textAlign = 'center';
context.textBaseline = 'top';
if (i % 10 === 0) {
context.font = `${fontSize}px Arial`;
context.fillText(
Math.round(i / 10) / (derivative / 10),
origin.x + (i - startValue / precision) * divide,
fontMarginTop
);
}
context.closePath();
}
}
init(options) {
this._renderBox(options.el);
}
}
export default sliderRuler;