Увидев заголовок, вы должны спросить:
Зачем делать еще одно колесо?
IScroll
Разве это не легко? ЕстьBetter-Scroll
А?
Обе эти библиотеки хороши, и я обычно ими пользуюсь по двум причинам:
- Не соответствует парадигме React:
ui = f(state)
Обе библиотеки являются кроссплатформенными и обе работают напрямую.dom
Да, кроссплатформенность не плоха, это однозначно хорошо, но в мире React, чтобы разобраться с синхронизацией состояния, обычно управляется состоянием или атрибутами, хотя можно обернуть две вышеуказанные библиотеки с React, чтобы обеспечить версия React, но она всегда кажется далеко не идеальной.
- Необоснованный спрос на продукцию
Команда предоставляет коммерческие продукты на стороне ПК для стороны B, что требует хорошего интерактивного опыта.
Продукт сказал: Можно ли изменить полосу прокрутки системы?
Я сказал: вы можете изменить это, Chrome может изменить это, но FireFox не может изменить это,
Продукт говорит: может ли мышь увеличиваться при наведении?
Я сказал: попробую, Эдег уже поддерживает, Хром работает тяжело, а другие вроде не работают
Продукт сказал: Посмотрите на полосу прокрутки в таблице здесь, вы можете вывести ее в сторону браузера?
Я сказал: Блин, эта форма внутри, там несколько гор от браузера, это отдельный компонент
Продукт говорит: Как мне перетащить страницу, чтобы страница прокручивалась, не перетаскивая полосу прокрутки
Я сказал: сенсорный экран на мобильном терминале в порядке, на ПК можно использовать тачпад
Продукт говорит: Почему моя сенсорная панель не работает
Я сказал: да, на Mac вам, кажется, нужно установить драйвер для сенсорной панели TinkPad, вы можете использовать колесико мыши.
Подводя итог, вы понимаете наши требования к продукту? Вот моя первая мысль:
Если хочешь расти, то перед лицом необоснованных требований продакт-менеджеров, если хочешь отказаться, то должен отказаться с чистой совестью
На самом деле отказаться легко, всегда есть причина, и стоимость самая низкая, но я всегда чувствую, что этого точно можно добиться, но к сожалению временные затраты немного высоки, так много багов, и это не может быть изменено два дня? Одного-двух дней может не хватить, так что не могу сказать, что есть какие-то проблемы
Конечно, я все равно отказался от продукта и сосредоточился на исправлении ошибок блокировки.Потом, воспользовавшись выходными, я хорошо придумал, что делать с этой полосой прокрутки.Это было неожиданностью для продукта, хотя он не понимала, как это жалко...
Цели дизайна
- Близок к родному, прост в использовании, легко переключается с полосы прокрутки по умолчанию на новую полосу прокрутки.
Родное письмо:
<div className="container"
style={{
width: 500,
height: 400,
overflow:'auto'
}}
onScroll={onScroll}
>
<div className="content" ref="content" style={{
width: 1000,
height: 800,
}}>
{content}
</div>
</div>
Только нужно изменить этикетку контейнера, после замены:
<Scroll className="container"
style={{
width: 500,
height: 400,
overflow:'auto'
}}
onScroll={onScroll}
>
<div className="content" ref="content" style={{
width: 1000,
height: 800,
}}>
{content}
</div>
</Scroll>
-
onScroll
Интерфейс соответствует оригиналу, не затрагивая исходную бизнес-логику
export interface IScrollEvent {
target: {
scrollLeft: number;
scrollTop: number;
};
}
export interface IScrollProps{
/**
* 滚动条距离左侧的距离
*/
scrollLeft?: number;
/**
* 滚动条距离顶部距离
*/
scrollTop?: number;
onScroll?: (e: IScrollEvent) => void;
}
- служба поддержки
scrollLeft
а такжеscrollTop
Свойство изменяет положение полосы прокрутки, а экземпляр объекта также предоставляет следующие свойства, совместимые с собственным API DOM.
export interface IScroll {
scrollLeft: number;
scrollTop: number;
/**
* 滚动到指定位置
*/
scrollTo: (left: number, top: number) => void;
/**
* 滚动相对距离
*/
scrollBy: (left: number, top: number) => void;
/**
* 重新计算滚动区域
*/
refresh: () => void;
}
проблемы, с которыми столкнулись
- Как заставить элементы двигаться
можно использовать绝对定位
илиtransform
,transform
предпочтительнее, потому что он может поддерживатьgpu
Ускорение, обработка анимации прокрутки будет лучше
- Как поддерживать перетаскивание
Конечно мониторингmousedown
,mousemove
,mouseup
, рассчитать направление движения мыши и относительное расстояние, а затем определить положение элемента, которое мобильный терминал должен отслеживатьtouchstart
, touchmove
, touchend
мероприятие
- Как поддерживать сенсорную панель
Проверьте это, сенсорная панель ПК может пойтиonwheel
Событие — это событие прокрутки колесика мыши, которое может поддерживать сенсорную панель, и мой ThinkPad также может его поддерживать.
- Как сделать анимацию прокрутки
Если использовать только полосы прокрутки, то без анимации можно обойтись, но при перетаскивании обязательно должна быть анимация прокрутки, как в CSS3ease-in
ease-out
анимация, вы можете использоватьsetInterval
илиrequestAnimationFrame
api, вы можете сначала сделать линейное движение с равномерным замедлением, а давайте поговорим о других анимациях.
- если поддерживается
ui = f(state)
Парадигма, частое изменение состояния, повторный рендеринг, есть ли проблемы с производительностью?
По сравнению с прямой модификацией dom производительность определенно снижена, но находится в пределах допустимого.
Вышеупомянутые проблемы в основном решаемы, и проблем с блокировкой нет.Следующее является реализацией:
выполнить
Перетаскивать
Перетаскивание мышиonMouseDown
onMouseMove
onMouseUp
События, общий процесс выглядит следующим образом:
-
onMouseDown
Запишите исходное положение мыши в событииpointStart
,дляdocument
регистрmousemove
а такжеmouseup
мероприятие -
onMouseMove
Мышь перемещается в событии, записываем текущую позицию точки мышиEnd, минусpopointEnd
-pointStart
Получить смещение мыши, установитьsecrollLeft
, страница прокручивается. -
onMouseUp
событие, получить мышь即时速度
, если скорость0
, то движение прекращается, если скорость больше0
, выполнить анимацию прокрутки, удалитьdocument
изmousemove
а такжеmouseup
мероприятие
Нет дополнения к корневому узлу области прокрутки
mousemove
а такжеmouseup
мероприятие, датьdocument
с мышьюmousemove
а такжеmouseup
событие, потому что диапазон движения мыши может превышать область прокрутки, если область прокрутки превышена, эти два события больше не будут выполняться
Мгновенный расчет скорости
После того, как мышь поднята, вам нужно знать скорость движения, а затем тормозить на этой скорости, поэтому вам нужно рассчитать即时速度
, здесь нельзя использовать среднюю скорость.
рассчитать即时速度
надо знать距离
а также时间
, после щелчка мышьюsetInverval
таймер, каждый100ms
Запишите положение и отметку времени мыши.После того, как мышь будет поднята, завершите расчет, получите текущую позицию и время и сделайте разницу с исторической позицией и отметкой времени, чтобы получить окончательный результат.100ms
Скорость в пределах , рассчитывается следующим образом:
/**
* 启动即时速度计算
*/
startCaclRealV = () => {
const me = this;
const t = _REAL_VELOCITY_TIMESPAN;
const timer = setInterval(() => {
if (!me.isDraging) {
clearInterval(timer);
return;
}
if (!me.lastPos) {
me.lastTime = Date.now();
me.lastPos = me.endPoint;
return;
}
me.lastTime = Date.now();
me.lastPos = me.endPoint;
}, t);
return {
destroy() {
clearInterval(timer);
},
}
}
/**
* 计算即时速度
*/
caclRealV = () => {
const me = this;
if (!me.lastPos) {
return {
realXVelocity:0,
realYVelocity:0
}
}
const time = (Date.now() - me.lastTime) / 1000;
const xdist = Math.abs(me.endPoint.x - me.lastPos.x);
const ydist = Math.abs(me.endPoint.y - me.lastPos.y);
return {
realXVelocity:caclVelocity(xdist, time),
realYVelocity:caclVelocity(ydist, time),
}
}
анимация прокрутки
После поднятия мыши即时速度
Начните движение замедления, здесь вы можете использовать функцию замедления для расчета положения, установитьscrollLeft
, здесь я использовал движение равномерного замедления, используяrequestAnimationFrame
Выполните цикл анимации, используяtransform: translate3d(0,${indicateTop}px,0)
Установите смещение, вы можете запустить ускорение пгу,
Ссылка на код:
import { TDirection, TPoint } from './types'
/**
* 动画执行函数
* @param v 速度 像素/秒
* @param a 减速度 像素/秒平方
* @param onMove 回调函数,返回移动距离
* @param onEnd 回调函数,终止动画
*/
export const animate = (v: number, a: number, onMove: (dist) => boolean, onEnd: () => void): { destroy: () => void } => {
const t = 16;// ms
const start = Date.now();
return loopByFrame(t, () => {
const time = (Date.now() - start) / 1000;
if (time === 0) {
return true;
}
const dist = move(v, a, time);
if (dist === 0) {
return false;
}
return onMove(dist);
}, onEnd);
}
/**
* 利用 requestAnimationFrame 执行动画循环
* @param duration 动画时间间隔,使用 requestAnimationFrame 不需要设置
* @param onMove 动画执行函数
* @param onEnd 动画终止函数
*/
export const loopByFrame = (duration = 16, onMove = () => true, onEnd = () => void 0): { destroy: () => void } => {
let animateFrame;
function step(func, end = () => void 0) {
if (!func) {
end();
return;
}
if (!func()) {
destroy();
end();
return;
}
animateFrame = window.requestAnimationFrame(() => {
step(func, end);
});
}
function destroy() {
if (animateFrame) {
window.cancelAnimationFrame(animateFrame);
}
}
step(onMove, onEnd);
return {
destroy,
}
}
/**
* 利用 setInterval 执行函数循环
* @param duration 时间间隔
* @param cb 回调函数
* @param onEnd 终止函数
*/
export const loopByInterval = (duration = 16, cb = () => true, onEnd = () => void 0): { destroy: () => void } => {
const timer = setInterval(() => {
if (!cb()) {
clearInterval(timer);
onEnd();
}
}, duration);
return {
destroy() {
clearInterval(timer);
onEnd();
},
}
}
/**
* 计算以速度 v ,减速度 a,运动 time 时间的距离
* @param v 速度
* @param a 减速度
* @param time 时间
*/
export const move = (v: number, a: number, time: number) => {
// 获取下一时刻速度,如果速度为 0 终止
const nextV = caclNextVelocity(v, a, time);
if (nextV <= 0) {
return 0;
}
// 计算下一刻的距离
const dist = caclDist(v, time, a);
return dist;
}
/**
* 计算滚动方向,暂时只支持横向滚动
* @param start 起始点
* @param end 终点
*/
export const caclDirection = (start: TPoint, end: TPoint): TDirection => {
const xLen = (end.x - start.x);
const yLen = (end.y - start.y);
if (Math.abs(xLen) > Math.abs(yLen)) {
return xLen > 0 ? 'right' : 'left';
} else {
return yLen > 0 ? 'bottom' : 'top';
}
}
/**
* 减速直线运动公式,计算距离
* @param v 速度
* @param t 时间 单位秒
* @param a 加速度
*/
export const caclDist = (v: number, t: number, a: number) => {
return v * t - (a * t * t) / 2;
}
/**
* 计算速度
* @param v0 初始速度
* @param a 加速度
* @param t 时间
*/
export const caclNextVelocity = (v0: number, a: number, t: number) => {
return v0 - a * t;
}
/**
* 计算速度
* @param dist 距离 单位像素
* @param time 时间 单位秒
*/
export const caclVelocity = (dist: number, time: number) => {
if (time <= 0) {
return 0;
}
return dist / time;
}
Синхронизация полосы прокрутки
Вы можете использовать этот режим, когда вам нужно синхронизировать несколько полос прокрутки на странице, например, в нашей системе необходимо синхронизировать заголовок, тело и панель инструментов.
Синхронизация полосы прокрутки Первоначально мы использовали собственную полосу прокрутки системы.scrollLeft
Атрибуты синхронизируются, но будет сильно лагать, сейчас б/уScroll
компоненты, используя CSS3transform
Для синхронизации эффект гораздо лучше.
Образец кода:
import React,{Component} from 'react';
class Demo extends Component{
constructor(props,context){
super(props,context);
const me=this;
me.state={
scrollLeft:0,
scrollTop:0,
}
}
onScroll=(e)=>{
const me=this;
me.setState({
scrollLeft:e.target.scrollLeft,
scrollTop:e.target.scrollTop
});
}
render(){
const me=this;
const {
scrollLeft,
scrollTop
}=me.state;
return (
<Scroll
scrollLeft={scrollLeft}
scrollTop={scrollTop}
className="container"
style={{ width: 500, height: 400,}}
onScroll={me.onScroll}
>
<div className="content" ref="content"
style={{width: 1000,height: 800}}>
</div>
</Scroll>
<Scroll
scrollLeft={scrollLeft}
scrollTop={scrollTop}
className="container"
style={{width: 500,height: 400,}}
onScroll={me.onScroll}
>
<div className="content" ref="content"
style={{width: 1000,height: 800,}}>
</div>
</Scroll>
)
}
}
Показать результаты
наконец
В этой статье делается попытка реализовать полосу прокрутки отдельно, чтобы решить проблему непоследовательного поведения полос прокрутки различных браузеров на стороне ПК. Она совместима с собственным API и может обеспечить плавное переключение. Общая сложность реализации умеренная. В основном необходимо обратить внимание на реализацию анимации плавности, и есть некоторые проблемы.Есть ли лучший способ?
-
即时速度
Расчет, есть ли лучший способ? - Текстовая анимация использует равномерное замедление линейного движения, последующие действия не могут обеспечить множество потребностей в анимации?
- Мобильный терминал в настоящее время не поддерживается, и можно ли поддерживать мобильный?