const BigOrders = ({workerRef, symbol}: BigOrdersProps) => {
  const [dpiScale, setDpiScale] = useState(Math.ceil(window.devicePixelRatio));
  const [canvasSize, setCanvasSize] = useState<CanvasSize>({height: 0, width: 0});
  const containerRef = useRef<HTMLDivElement|null>(null);
  const canvasRef = useRef<HTMLCanvasElement|null>(null);

 const precision = useSelector((state: AppState) => state.binancePrecision.futures);

  const {symbols} = useBinanceSymbols();

  const cupSubscribe = useCallback(async(symbol: string, precision: {[key: string]: Precision}, symbols: string[]) => {
    workerRef.current?.postMessage(JSON.stringify({type: "subscribe", symbol, precision, symbols}));
  }, []);

  const cupUnsubscribe = useCallback(async(pair: string) => {
    workerRef.current?.postMessage(JSON.stringify({type: "unsubscribe", symbol}));
  }, []);

  const wheelHandler = (e: WheelEvent) => {
    e.preventDefault();
    workerRef.current?.postMessage(JSON.stringify({type: e.deltaY < 0 ? "camera_up" : "camera_down"}));
  };

  useEffect(() => {
    workerRef.current = new Worker(new URL("/workers/big-order-builder.ts", import.meta.url));
    canvasRef.current?.addEventListener("wheel", wheelHandler, {passive: false});

    return () => {
      workerRef.current?.terminate();
      canvasRef.current?.removeEventListener("wheel", wheelHandler);
    };
  }, []);

useEffect(() => {
if (!workerRef.current) return;
let animationFrameId: number|null = null;

interface UpdateBigOrderEvent {
type: “update_big_order”,
id: number,
symbol: string,
price: number,
quantity: number,
pricePrecision: number,
quantityPrecision: number,
}
workerRef.current.onmessage = (event: MessageEvent<UpdateBigOrderEvent>) => {
// console.log(event?.data?.id);

if (event?.data?.type === “update_big_order”) {
if (null !== animationFrameId) {
// console.log(animationFrameId);

cancelAnimationFrame(animationFrameId);
}

animationFrameId = requestAnimationFrame(() => {
const data = event.data as UpdateBigOrderEvent;
// console.log(data);

const context = canvasRef.current?.getContext(“2d”);

if (context) {
// cupDrawer.clear(context, canvasSize);

const rowsOnScreenCount = cupTools.getRowsCountOnScreen(
canvasSize.height,
bigOrdersOptions(darkMode).cell.defaultHeight * dpiScale,
);
const realCellHeight = parseInt((canvasSize.height / rowsOnScreenCount).toFixed(0));

if (data.price !== 0) {
bigOrdersDrawer.draw(
context,
canvasSize,
dpiScale,
data.id,
data.symbol,
data.price,
data.quantity,
data.pricePrecision,
data.quantityPrecision,
realCellHeight,
);
}
}
});
}
};

return () => {
if (null !== animationFrameId) {
cancelAnimationFrame(animationFrameId);
}
};
}, [workerRef.current, canvasSize, darkMode, dpiScale, isLoaded]);
return <div ref={containerRef} className={styles.canvasWrapper}>
<canvas
ref={canvasRef}
className={styles.canvas}
width={canvasSize?.width}
height={canvasSize?.height}
/>
</div>;
};

const bigOrdersDrawer = {
clear: (ctx: CanvasRenderingContext2D, size: CanvasSize) => {
ctx.clearRect(0, 0, size.width, size.height);
},
draw: (
ctx: CanvasRenderingContext2D,
size: CanvasSize,
dpiScale: number,
id: number,
symbol: string,
price: number,
quantity: number,
pricePrecision: number,
quantityPrecision: number,
cellHeight: number,
) => {
const quantityDivider = Math.pow(10, quantityPrecision);
const priceDivider = Math.pow(10, pricePrecision);


const yPosition = cellHeight * id;

bigOrdersDrawer.drawSymbol(
ctx,
symbol,
yPosition,
cellHeight,
size.width,
dpiScale,
true
);

bigOrdersDrawer.drawPrice(
ctx,
price / priceDivider,
yPosition,
cellHeight,
size.width,
dpiScale,
true
);

bigOrdersDrawer.drawQuantity(
ctx,
quantity / quantityDivider,
yPosition,
cellHeight,
size.width,
dpiScale,
true
);

drawSymbol: (
ctx: CanvasRenderingContext2D,
symbol: string,
yPosition: number,
cellHeight: number,
fullWidth: number,
dpiScale: number,
darkMode: boolean,
) => {
ctx.textBaseline = “middle”;
ctx.font = ${bigOrdersOptions().font.weight} ${bigOrdersOptions().font.sizePx * dpiScale}px ${bigOrdersOptions().font.family};
ctx.fillStyle = ${bigOrdersOptions().font.color};
ctx.fillText(
symbol,
0,
yPosition + cellHeight / 2,
);
},
drawPrice: (
ctx: CanvasRenderingContext2D,
price: number,
yPosition: number,
cellHeight: number,
fullWidth: number,
dpiScale: number,
darkMode: boolean,
) => {
ctx.textBaseline = “middle”;
ctx.font = ${bigOrdersOptions().font.weight} ${bigOrdersOptions().font.sizePx * dpiScale}px ${bigOrdersOptions().font.family};
ctx.fillStyle = ${bigOrdersOptions().font.color};
ctx.fillText(
price.toString(),
(fullWidth / 2) - (bigOrdersOptions().cell.paddingX * dpiScale),
yPosition + cellHeight / 2,
);
},
drawQuantity: (
ctx: CanvasRenderingContext2D,
quantity: number,
yPosition: number,
cellHeight: number,
fullWidth: number,
dpiScale: number,
darkMode: boolean,
) => {
const {width: textWidth} = ctx.measureText(quantity.toString());
ctx.textBaseline = “middle”;
ctx.font = ${bigOrdersOptions().font.weight} ${bigOrdersOptions().font.sizePx * dpiScale}px ${bigOrdersOptions().font.family};
ctx.fillStyle = ${bigOrdersOptions().font.color};
ctx.fillText(
quantity.toString(),
fullWidth - bigOrdersOptions().cell.paddingX * dpiScale - textWidth,
yPosition + cellHeight / 2,
);
},
};

export default bigOrdersDrawer;

у нас отрисовываются bigOrdersDrawer.draw, умножается на id и этим высчитывает высоту, получается столбик с данными.
Нужно, чтобы когда они отрисовываются в самом низу, то канвас начинал скроллиться и показывать внизу последние добавленные, так же нужно, чтобы можно было скроллить вверх и вниз