diff --git a/src/routes/player/PreviewBar.tsx b/src/routes/player/PreviewBar.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..3d1710aede0abf814374463e9c057389be821751
--- /dev/null
+++ b/src/routes/player/PreviewBar.tsx
@@ -0,0 +1,55 @@
+import React, {useMemo} from "react";
+import {createUseStyles} from "react-jss";
+import {useOffsetRef} from "../../util/offset/useOffsetRef";
+import {PreviewViewer} from "./PreviewViewer";
+
+interface Props {
+    previewTrack: HTMLTrackElement | null,
+    duration: number,
+    position: number,
+}
+
+export function PreviewBar({previewTrack, duration, position}: Props) {
+    const classes = useStyles();
+
+    const [previewBarRef, previewHeadRef, offset] = useOffsetRef(position);
+
+    return useMemo(() => (
+        <div
+            ref={previewBarRef}
+            className={classes.previewBar}
+        >
+            <div
+                ref={previewHeadRef}
+                className={classes.previewHead}
+                style={{
+                    transform: `translate3d(${offset}px, 0, 0)`
+                }}
+            >
+                <PreviewViewer
+                    previewTrack={previewTrack}
+                    position={position * duration}
+                />
+            </div>
+        </div>
+    ), [classes.previewHead, classes.previewBar, duration, offset, position, previewBarRef, previewHeadRef, previewTrack]);
+}
+
+const useStyles = createUseStyles({
+    previewBar: {
+        position: "relative",
+        width: "40rem",
+        height: "6rem",
+        background: "#7c7",
+    },
+    previewHead: {
+        position: "absolute",
+        top: 0,
+        bottom: 0,
+        left: "-5rem",
+        width: "10rem",
+        display: "flex",
+        flexDirection: "column-reverse",
+        background: "#f00",
+    }
+});
diff --git a/src/routes/player/PreviewViewer.tsx b/src/routes/player/PreviewViewer.tsx
index 3ba98292639a73a600964c19443fd6988c07217f..550508a8d0a1d6fbb60db6f9b5fb2f147ff2e75a 100644
--- a/src/routes/player/PreviewViewer.tsx
+++ b/src/routes/player/PreviewViewer.tsx
@@ -1,22 +1,32 @@
 import React, {useMemo} from "react";
+import {createUseStyles} from "react-jss";
 import {useImage} from "../../util/media/useImage";
 import {useTextTrackCues} from "../../util/media/useTextTrackCues";
 import {parseImageSprite} from "../../util/sprite/parseImageSprite";
 import {useImageSprite} from "../../util/sprite/useImageSprite";
 
 interface Props {
-    previewTrack: HTMLTrackElement,
-    position: number,
+    previewTrack: HTMLTrackElement | null,
+    position: number | null,
 }
 
 export function PreviewViewer({previewTrack, position}: Props) {
+    const classes = useStyles();
+
     const cues = useTextTrackCues(previewTrack);
-    const activeCue = cues.find(it => it.startTime <= position && it.endTime >= position)
-    const activeUrl = activeCue ? new URL(activeCue.text, previewTrack.src).toString() : null;
+    const activeCue = position === null ? null : cues.find(it => it.startTime <= position && it.endTime >= position);
+    const activeUrl = previewTrack && activeCue ? new URL(activeCue.text, previewTrack.src).toString() : null;
     const imageSprite = useMemo(() => parseImageSprite(activeUrl), [activeUrl]);
     const image = useImage(imageSprite?.src || null);
     const sprite = useImageSprite(imageSprite, image);
     return useMemo(() => (
-        <img alt="" src={sprite || undefined}/>
+        <img alt="" className={classes.preview} src={sprite || undefined}/>
     ), [sprite]);
 }
+
+const useStyles = createUseStyles({
+    preview: {
+        maxWidth: "100%",
+        maxHeight: "100%",
+    }
+});
diff --git a/src/routes/player/SeekBar.tsx b/src/routes/player/SeekBar.tsx
index 4efb327550c82b777533cc755ceda7a249681c33..00b6db0cdb0b0f974ebe237fb891f7d0f2b2111c 100644
--- a/src/routes/player/SeekBar.tsx
+++ b/src/routes/player/SeekBar.tsx
@@ -1,6 +1,7 @@
-import React, {MouseEvent, useCallback, useMemo, useRef, useState} from "react";
+import React, {MouseEvent, useCallback, useMemo, useState} from "react";
 import {createUseStyles} from "react-jss";
-import {PreviewViewer} from "./PreviewViewer";
+import {useOffsetRef} from "../../util/offset/useOffsetRef";
+import {PreviewBar} from "./PreviewBar";
 
 interface Props {
     previewTrack: HTMLTrackElement | null,
@@ -27,10 +28,11 @@ function getMousePosition(event: MouseEvent<HTMLDivElement>): MousePosition | nu
 export function SeekBar({video, previewTrack, duration, position}: Props) {
     const classes = useStyles();
 
-    const seekHeadRef = useRef<HTMLDivElement | null>(null);
     const [seekPosition, setSeekPosition] = useState<number | null>(null);
     const isVisible = seekPosition !== null;
 
+    const [seekBarRef, seekHeadRef, offset] = useOffsetRef(seekPosition || 0);
+
     const onMouseLeave = useCallback(() => {
         setSeekPosition(null)
     }, [setSeekPosition]);
@@ -50,38 +52,35 @@ export function SeekBar({video, previewTrack, duration, position}: Props) {
         if (position === null) {
             return;
         }
-        if (seekHeadRef.current) {
-            seekHeadRef.current.style.transform = `translate3d(${position.absolute}px, 0, 0)`
-        }
         window.requestAnimationFrame(() => {
-            setSeekPosition(position.relative * duration)
+            setSeekPosition(position.relative)
         })
-    }, [duration]);
+    }, []);
 
     const seekBar = useMemo(() => (
         <div
+            ref={seekBarRef}
             className={classes.seekBar}
             onMouseLeave={onMouseLeave}
             onClick={onClick}
             onMouseMove={onMouseMove}
         >
             <div
-                className={classes.seekHead}
                 ref={seekHeadRef}
+                className={classes.seekHead}
                 style={{
-                    opacity: isVisible ? 1 : 0
+                    opacity: isVisible ? 1 : 0,
+                    transform: `translate3d(${offset}px, 0, 0)`
                 }}
             />
         </div>
-    ), [classes.seekBar, classes.seekHead, isVisible, onClick, onMouseLeave, onMouseMove]);
+    ), [classes.seekBar, classes.seekHead, isVisible, offset, onClick, onMouseLeave, onMouseMove, seekBarRef, seekHeadRef]);
 
     return (
-        <div>
+        <React.Fragment>
+            <PreviewBar previewTrack={previewTrack} duration={duration} position={seekPosition || 0}/>
             {seekBar}
-            {seekPosition !== null && previewTrack && (
-                <PreviewViewer previewTrack={previewTrack} position={seekPosition}/>
-            )}
-        </div>
+        </React.Fragment>
     )
 }
 
diff --git a/src/util/media/useTextTrackCues.ts b/src/util/media/useTextTrackCues.ts
index 1309d104f1ba5fafef51cbf46d4ae2f0d6aa4922..557a77431b3e0642b4b429f49dcbeffbe1087707 100644
--- a/src/util/media/useTextTrackCues.ts
+++ b/src/util/media/useTextTrackCues.ts
@@ -1,23 +1,25 @@
 import {useEffect, useState} from "react";
 
-export const useTextTrackCues = (track: HTMLTrackElement) => {
+export const useTextTrackCues = (track: HTMLTrackElement | null) => {
     const [cues, setCues] = useState<TextTrackCue[]>([]);
     useEffect(() => {
-        track.track.mode = "hidden";
-        if (track.readyState >= 1) {
-            setCues(Array.from(track.track.cues || []))
-        } else {
-            let animationFrame: number | null = null;
-            const listener = () => {
-                animationFrame = window.requestAnimationFrame(() => {
-                    setCues(Array.from(track.track.cues || []))
-                })
-            };
-            track.addEventListener("load", listener)
-            return () => {
-                track.removeEventListener("load", listener);
-                if (animationFrame) {
-                    window.cancelAnimationFrame(animationFrame);
+        if (track !== null) {
+            track.track.mode = "hidden";
+            if (track.readyState >= 1) {
+                setCues(Array.from(track.track.cues || []))
+            } else {
+                let animationFrame: number | null = null;
+                const listener = () => {
+                    animationFrame = window.requestAnimationFrame(() => {
+                        setCues(Array.from(track.track.cues || []))
+                    })
+                };
+                track.addEventListener("load", listener)
+                return () => {
+                    track.removeEventListener("load", listener);
+                    if (animationFrame) {
+                        window.cancelAnimationFrame(animationFrame);
+                    }
                 }
             }
         }
diff --git a/src/util/offset/useOffset.ts b/src/util/offset/useOffset.ts
new file mode 100644
index 0000000000000000000000000000000000000000..05cdc5720992c343ef5374e897e8c8eb53207953
--- /dev/null
+++ b/src/util/offset/useOffset.ts
@@ -0,0 +1,11 @@
+import {useWidth} from "./useWidth";
+
+export const useOffset = <T extends HTMLElement, U extends HTMLElement>(parent: T | null, child: U | null, position: number) => {
+    const parentWidth = useWidth(parent);
+    const childWidth = useWidth(child);
+
+    const offset = parentWidth * position;
+    const minOffset = childWidth / 2;
+    const maxOffset = parentWidth - minOffset;
+    return Math.max(minOffset, Math.min(offset, maxOffset));
+}
diff --git a/src/util/offset/useOffsetRef.ts b/src/util/offset/useOffsetRef.ts
new file mode 100644
index 0000000000000000000000000000000000000000..cad16f6bb4ad1acf39f8dc03bf858d3b43acbfba
--- /dev/null
+++ b/src/util/offset/useOffsetRef.ts
@@ -0,0 +1,10 @@
+import {useState} from "react";
+import {useOffset} from "./useOffset";
+
+export const useOffsetRef = <T extends HTMLElement, U extends HTMLElement>(position: number):
+    [(it: T | null) => void, (it: U | null) => void, number] => {
+    const [parent, setParent] = useState<T | null>(null);
+    const [child, setChild] = useState<U | null>(null);
+
+    return [setParent, setChild, useOffset(parent, child, position)];
+}
diff --git a/src/util/offset/useWidth.ts b/src/util/offset/useWidth.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6d53d44abb2b308c23a3d6da8a26f2be7a24049d
--- /dev/null
+++ b/src/util/offset/useWidth.ts
@@ -0,0 +1,23 @@
+import {useEffect, useState} from "react";
+
+export const useWidth = <T extends HTMLElement>(it: T | null) => {
+    const [width, setWidth] = useState<number>(0);
+    useEffect(() => {
+        let animationFrame: number | null = null;
+        const listener = () => {
+            animationFrame = window.requestAnimationFrame(() => {
+                animationFrame = null;
+                setWidth(it?.offsetWidth || 0);
+            });
+        };
+        listener();
+        window.addEventListener("resize", listener);
+        return () => {
+            window.removeEventListener("resize", listener);
+            if (animationFrame !== null) {
+                window.cancelAnimationFrame(animationFrame);
+            }
+        }
+    }, [it]);
+    return width;
+}