Implementing Front‑End File Preview for Various Formats Using React
This article explains how to preview a wide range of file types—including images, audio, video, markdown, plain text, Office documents, PDFs and external websites—in a React front‑end by using native HTML tags, embed/iframe techniques, and libraries such as marked and highlight.js, while also supporting page or time positioning.
Introduction
A recent requirement asked the front‑end to upload files with specific extensions and provide in‑page preview functionality. The article walks through the solution, covering preview strategies for different file types and how to locate specific pages, timestamps, or text positions.
Supported File Types
The preview must handle the following formats: png、jpg、jpeg、docx、xlsx、ppt、pdf、md、txt、audio、video . For some formats (e.g., pdf , txt , markdown ) additional positioning features are required.
Implementation Overview
Self‑contained media files: png、jpg、jpeg、audio、video
Plain‑text files: markdown & txt
Office files: docx、xlsx、ppt
Embedded PDFs: pdf
External websites via iframe
Self‑Contained Media Files
Images ( png、jpg、jpeg )
Use the native <img> tag:
<img src={url} key={docId} alt={name} width="100%" />Audio ( audio )
Render with the <audio> element and disable download:
<audio ref={audioRef} controls controlsList="nodownload" style={{ width: '100%' }}>
<track kind="captions" />
<source src={url} type="audio/mpeg" />
</audio>Video ( video )
Render with the <video> element and mute by default:
<video ref={videoRef} controls muted controlsList="nodownload" style={{ width: '100%' }}>
<track kind="captions" />
<source src={url} type="video/mp4" />
</video>Full component for audio/video positioning (time in seconds):
import React, { useRef, useEffect } from 'react';
interface IProps {
type: 'audio' | 'video';
url: string;
timeInSeconds: number;
}
function AudioAndVideo(props: IProps) {
const { type, url, timeInSeconds } = props;
const videoRef = useRef<HTMLVideoElement>(null);
const audioRef = useRef<HTMLAudioElement>(null);
useEffect(() => {
const secondsTime = timeInSeconds / 1000;
if (type === 'audio' && audioRef.current) {
audioRef.current.currentTime = secondsTime;
}
if (type === 'video' && videoRef.current) {
videoRef.current.currentTime = secondsTime;
}
}, [type, timeInSeconds]);
return (
<div>
{type === 'audio' ? (
<audio ref={audioRef} controls controlsList="nodownload" style={{ width: '100%' }}>
<track kind="captions" />
<source src={url} type="audio/mpeg" />
</audio>
) : (
<video ref={videoRef} controls muted controlsList="nodownload" style={{ width: '100%' }}>
<track kind="captions" />
<source src={url} type="video/mp4" />
</video>
)}
</div>
);
}
export default AudioAndVideo;Plain‑Text Files ( markdown & txt )
When only a URL is available, the file must be fetched first. For markdown, the component uses marked to convert to HTML and highlight.js for code highlighting. Links open in a new tab, and the first match of the search text is scrolled into view.
Markdown Viewer
Key helper to highlight the first occurrence:
const highlightAndMarkFirst = (text: string, highlightText: string) => {
let firstMatchDone = false;
const regex = new RegExp(`(${highlightText})`, 'gi');
return text.replace(regex, (match) => {
if (!firstMatchDone) {
firstMatchDone = true;
return `<span id='first-match' style="color: red;">${match}</span>`;
}
return `<span style="color: red;">${match}</span>`;
});
};Configuration of marked for code highlighting:
marked.use({
renderer: {
code(code, infostring) {
const validLang = !!(infostring && hljs.getLanguage(infostring));
const highlighted = validLang
? hljs.highlight(code, { language: infostring, ignoreIllegals: true }).value
: code;
return `<pre><code class="hljs ${infostring}">${highlighted}</code></pre>`;
},
link(href, title, text) {
const isExternal = !href.startsWith('/') && !href.startsWith('#');
if (isExternal) {
return `<a href="${href}" title="${title}" target="_blank" rel="noopener noreferrer">${text}</a>`;
}
return `<a href="${href}" title="${title}">${text}</a>`;
},
},
});Full React component:
import React, { useEffect, useState, useRef } from 'react';
import { marked } from 'marked';
import hljs from 'highlight.js';
const preStyle = {
width: '100%',
maxHeight: '64vh',
minHeight: '64vh',
overflow: 'auto',
};
function MarkdownViewer({ docUrl, searchText }: { docUrl: string; searchText: string }) {
const [markdown, setMarkdown] = useState('');
const markdownRef = useRef<HTMLDivElement | null>(null);
const highlightAndMarkFirst = (text: string, highlightText: string) => {
let firstMatchDone = false;
const regex = new RegExp(`(${highlightText})`, 'gi');
return text.replace(regex, (match) => {
if (!firstMatchDone) {
firstMatchDone = true;
return `<span id='first-match' style="color: red;">${match}</span>`;
}
return `<span style="color: red;">${match}</span>`;
});
};
useEffect(() => {
fetch(docUrl)
.then((res) => res.text())
.then((text) => {
const highlighted = searchText ? highlightAndMarkFirst(text, searchText) : text;
setMarkdown(highlighted);
})
.catch((err) => console.error('Failed to load markdown:', err));
}, [docUrl, searchText]);
useEffect(() => {
if (markdownRef.current) {
marked.use({
renderer: {
code(code, infostring) {
const validLang = !!(infostring && hljs.getLanguage(infostring));
const highlighted = validLang
? hljs.highlight(code, { language: infostring, ignoreIllegals: true }).value
: code;
return `<pre><code class="hljs ${infostring}">${highlighted}</code></pre>`;
},
link(href, title, text) {
const isExternal = !href.startsWith('/') && !href.startsWith('#');
if (isExternal) {
return `<a href="${href}" title="${title}" target="_blank" rel="noopener noreferrer">${text}</a>`;
}
return `<a href="${href}" title="${title}">${text}</a>`;
},
},
});
const html = marked.parse(markdown);
markdownRef.current.innerHTML = html as string;
const firstMatch = document.getElementById('first-match');
if (firstMatch) {
firstMatch.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
}
}, [markdown]);
return (
<div style={preStyle}>
<div ref={markdownRef} />
</div>
);
}
export default MarkdownViewer;Text File Viewer
Highlights search text and scrolls to the first matching paragraph:
function highlightText(text: string) {
if (!searchText.trim()) return text;
const regex = new RegExp(`(${searchText})`, 'gi');
return text.replace(regex, `<span style="color: red">$1</span>`);
} import React, { useEffect, useState, useRef } from 'react';
import { preStyle } from './config';
function TextFileViewer({ docurl, searchText }: { docurl: string; searchText: string }) {
const [paragraphs, setParagraphs] = useState<string[]>([]);
const targetRef = useRef<HTMLDivElement | null>(null);
function highlightText(text: string) {
if (!searchText.trim()) return text;
const regex = new RegExp(`(${searchText})`, 'gi');
return text.replace(regex, `<span style="color: red">$1</span>`);
}
useEffect(() => {
fetch(docurl)
.then((res) => res.text())
.then((text) => {
const highlighted = highlightText(text);
const paras = highlighted.split('\n').map(p => p.trim()).filter(p => p);
setParagraphs(paras);
})
.catch((err) => console.error('Failed to load text file:', err));
}, [docurl, searchText]);
useEffect(() => {
const timer = setTimeout(() => {
if (targetRef.current) {
targetRef.current.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
}, 100);
return () => clearTimeout(timer);
}, [paragraphs]);
return (
<div style={preStyle}>
{paragraphs.map((para, idx) => {
const isTarget = para.includes(`>${searchText}<`);
return (
<p key={idx} ref={isTarget && !targetRef.current ? targetRef : null}>
<div dangerouslySetInnerHTML={{ __html: para }} />
</p>
);
})}
</div>
);
}
export default TextFileViewer;Office Files ( docx、xlsx、ppt )
Preview using Microsoft Office Online embed URL:
<iframe
src={`https://view.officeapps.live.com/op/view.aspx?src=${url}`}
width="100%"
height="500px"
frameBorder="0"
></iframe>Embedded PDF ( pdf )
Use the <embed> tag; page number can be appended to the URL:
<embed src={`${httpsUrl}`} style={preStyle} key={`${httpsUrl}`} /> const httpsUrl = sourcePage
? `${doc.url}#page=${sourcePage}`
: doc.url;External Websites ( iframe )
Render any external URL inside an <iframe> :
<iframe
title="Website"
width="100%"
height="100%"
src={doc.url}
allow="microphone;camera;midi;encrypted-media;"
/>Conclusion
The article covered preview implementations for all required file types, including positioning logic for PDFs, markdown, and plain text, and provided complete React code snippets for each scenario.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.