前端使用pdf.js渲染pdf文件解決方案
一、前言
在前端開發(fā)中,渲染PDF文件一直是一項(xiàng)重要而挑戰(zhàn)性的任務(wù)。而今,我們可以借助pdf.js庫(kù)來輕松實(shí)現(xiàn)這一目標(biāo)。pdf.js是一個(gè)開源的JavaScript庫(kù),它可以在瀏覽器中渲染PDF文件,實(shí)現(xiàn)了在網(wǎng)頁(yè)上查看PDF文檔的功能。它提供了豐富的API和功能,使得在前端頁(yè)面展示PDF文件變得輕而易舉。讓我們一起探索pdf.js的奇妙之處,輕松實(shí)現(xiàn)前端PDF文件的渲染與展示吧!
二、簡(jiǎn)介
1、pdf.js介紹
pdf.js是一款基于JavaScript的開源PDF閱讀器組件,可以在網(wǎng)頁(yè)中直接顯示和操作PDF文件,目前已知的前端渲染pdf組件都是基于pdf.js進(jìn)行封裝。
git地址:https://github.com/mozilla/pdf.js
注:開源且免費(fèi)
2、插件版本參數(shù)
插件 | 版本 |
Node | v22.13.0 |
@types/react | ^18.0.33 |
@types/react-dom | ^18.0.11 |
pdfjs-2.5.207-es5-dist.zip (viewer.js使用方式) | 2.5.207 |
pdfjs-dist (canvas渲染方式) | 3.6.172 |
三、通過viewer.html實(shí)現(xiàn)預(yù)覽(推薦)
1、介紹
除了PDF預(yù)覽,還待配套的工具欄,支持功搜索、縮放、目錄、打印等功能~
Demo如圖:
2、部署
【1】下載插件包
下載地址:https://github.com/mozilla/pdf.js/releases/tag/v2.5.207
【2】客戶端方式
把下載后的pdfjs-2.5.207-es5-dist.zip解壓后,放在項(xiàng)目中的public文件夾下
【3】服務(wù)端方式
pdf.js包仍然放在public目錄下(或者服務(wù)端目錄下)
const pdfServerUrl = '/pdfjs-2.5.207-es5-dist/web/viewer.html'
一個(gè)可以獲取pdf文件二進(jìn)制流的地址(需要同源)
.... const pdfInfoUrl = `${location.origin}/xxx/xx.pdf`; const url = `${pdfServerUrl}?file=${encodeURIComponent(pdfInfoUrl)}` ... <iframe id='pdfIframe' src={url} width="100%" height="100%"></iframe>
3、使用方法
【1】預(yù)覽PDF文件
1)客戶端方式(基于React框架為例)
const viewPDF: React.FC = () => { // pdf文件路徑,放在項(xiàng)目的public目錄下 const pdfUrl = '/A.pdf'; //pdf.js庫(kù)的代碼,放在項(xiàng)目的public目錄下 const pdfServerUrl = '/pdfjs-2.5.207-es5-dist/web/viewer.html' const url = `${pdfServerUrl}?file=${pdfUrl}` return <> <h1>pdf 搜索(基于pdf-dist,pdf_viewer.html)</h1> <iframe id='pdfIframe' src={url} width="100%" height="100%"></iframe> </>; }
2)服務(wù)端方式
通過axios接口獲取文件的arraybuffer,再把arraybuffer轉(zhuǎn)換成二進(jìn)制Blob,最后把Bolb轉(zhuǎn)成blob:url傳給viewer.html
... import axios from 'axios'; ... const [pdfUrl, setPdfUrl] = useState<string>(''); const getPDFViewUrl = (fileName: any) => { axios({ method: 'get', url: `URL`,//文件下載的url responseType: 'arraybuffer' }).then(response =>{ const blob = new Blob([response.data], { type: 'application/pdf' }); const blobUrl = URL.createObjectURL(blob); console.log('zyk===>', blobUrl) setPdfUrl(`${pdfServerUrl}?file=${blobUrl}`) }) }; return ( <> <iframe id="pdfIframe" src={pdfUrl} width="100%" height="100%"></iframe> </> );
【2】外部搜索條件觸發(fā)pdf.js的搜索邏輯
- 跳轉(zhuǎn)至第一個(gè)匹配的內(nèi)容
- 匹配內(nèi)容高亮
const viewPDF: React.FC = () => { // pdf文件路徑,放在項(xiàng)目的public目錄下 const pdfUrl = '/A.pdf'; //pdf.js庫(kù)的代碼,放在項(xiàng)目的public目錄下 const pdfServerUrl = '/pdfjs-2.5.207-es5-dist/web/viewer.html' const url = `${pdfServerUrl}?file=${pdfUrl}` let pdfContentWindow: any = null; //緩存iframContent const getPdfContent = () => { const pdfFrame: any = document.getElementById('pdfIframe'); if (!pdfFrame) { return; } pdfContentWindow = pdfFrame.contentWindow; //pdf組件部分信息,包括:當(dāng)前頁(yè)碼、總共頁(yè)碼等 console.log('page===>', pdfContentWindow.PDFViewerApplication); } const onSearchForOut = (searchText: string) => { pdfContentWindow.postMessage(searchText, '*'); pdfContentWindow.addEventListener('message', (e: any) => { // 高亮匹配結(jié)果 pdfContentWindow.PDFViewerApplication.findBar.findField.value = e.data; pdfContentWindow.PDFViewerApplication.findBar.highlightAll.checked = true; pdfContentWindow.PDFViewerApplication.findBar.dispatchEvent('highlightallchange'); //觸發(fā)搜索項(xiàng)‘下一個(gè)'事件 pdfContentWindow.PDFViewerApplication.findBar.dispatchEvent('again', false); }, false); } useEffect(() => { getPdfContent(); setTimeout(() => { // 外部的搜索條件 onSearchForOut('陽(yáng)區(qū)CBD核心區(qū)') }, 3* 1000) }, []); return <> <h1>pdf 搜索(基于pdf-dist,pdf_viewer.html)</h1> <iframe id='pdfIframe' src={url} width="100%" height="100%"></iframe> </>; }
四、把pdf渲染為canvas實(shí)現(xiàn)預(yù)覽
1、安裝
npm install pdfjs-dist --save
2、功能實(shí)現(xiàn)
【1】實(shí)現(xiàn)pdf預(yù)覽
import { Button } from 'antd'; import { useState, useEffect, useRef } from 'react'; import * as pdfjsLib from 'pdfjs-dist'; // 引入pdfjs-dist const pdfUrl = '/zyk.pdf'; // pdf 文件路徑,pdf文件存放于public目錄下 const workerUrl = `/pdf.worker.min.js`; //webworker存放于public目錄下 pdfjsLib.GlobalWorkerOptions.workerSrc = workerUrl; const viewPdf = (props: {height: string}) => { const {height} = props; const pdfContainerRef = useRef<any>(null); const [pagesList, setPagesList] = useState<any>([]); const scale = 2; // 縮放比例 // 渲染單個(gè)頁(yè)面 const renderPage = async (page: any, pageNumber: number) => { const viewport = page.getViewport({ scale }); const pageContentDom = document.createElement('div'); pageContentDom.id = `pdfPage-content-${pageNumber}`; pageContentDom.style.width = `${viewport.width}px`; pageContentDom.style.height = `${viewport.height}px`; pageContentDom.style.position = 'relative'; // 創(chuàng)建 Canvas 元素 const canvas = document.createElement('canvas'); const context = canvas.getContext('2d'); canvas.id=`pdfPage-${pageNumber}` canvas.width = viewport.width; canvas.height = viewport.height; canvas.style.border = '1px solid black'; pageContentDom.appendChild(canvas); pdfContainerRef.current.appendChild(pageContentDom); // 渲染 PDF 頁(yè)面到 Canvas await page.render({ canvasContext: context, viewport, }).promise; }; // 渲染 PDF 頁(yè)面 const renderPagesGroup = ( pages: any) => { pages.forEach(({page}:any, index: number) => { renderPage(page, index); }); }; // 加載 PDF 文件 const loadPdf = async (url: any) => { const pdf = await pdfjsLib.getDocument(url).promise; const pages: any[] = []; for (let i = 1; i <= pdf.numPages; i++) { const page = await pdf.getPage(i); const textContent = await page.getTextContent(); pages.push({ page, textContent }); } setPagesList(pages); renderPagesGroup(pages); }; useEffect(() => { loadPdf(pdfUrl); }, []); return <> <div> <h1>PDF 搜索(基于@pdfjs-dist-自定義實(shí)現(xiàn))</h1> <div> <div style={{ height: height || '500px' }}> {/* PDF 容器 */} <div ref={pdfContainerRef} style={{ position: 'relative', height: '100%', overflowY: 'scroll' }} /> </div> </div> </div> </> }; export default viewPdf;
【2】實(shí)現(xiàn)pdf內(nèi)容文本可選進(jìn)行復(fù)制
... //基于“【1】實(shí)現(xiàn)pdf預(yù)覽”代碼, 修改renderPage方法 // 渲染單個(gè)頁(yè)面 const renderPage = async (page: any, pageNumber: number) => { const viewport = page.getViewport({ scale }); const pageContentDom = document.createElement('div'); pageContentDom.id = `pdfPage-content-${pageNumber}`; //add-begin: 文本可選則 為了文本層和canvas層重疊,利用組件庫(kù)的類名(類名不能修改) pageContentDom.className = 'pdfViewer'; pageContentDom.style.setProperty('--scale-factor', scale as any); //add-end: 文本可選則 pageContentDom.style.width = `${viewport.width}px`; pageContentDom.style.height = `${viewport.height}px`; pageContentDom.style.position = 'relative'; // 創(chuàng)建 Canvas 元素 const canvas = document.createElement('canvas'); const context = canvas.getContext('2d'); canvas.id=`pdfPage-${pageNumber}` canvas.width = viewport.width; canvas.height = viewport.height; canvas.style.border = '1px solid black'; pageContentDom.appendChild(canvas); createHeightLightCanvas(viewport, pageNumber, pageContentDom); pdfContainerRef.current.appendChild(pageContentDom); // 渲染 PDF 頁(yè)面到 Canvas await page.render({ canvasContext: context, viewport, }).promise; //add-begin: 文本可選則 const textLayerDiv = document.createElement('div'); textLayerDiv.style.width = viewport.width; textLayerDiv.style.height = viewport.height; //為了文本層和canvas層重疊,利用組件庫(kù)的類名 textLayerDiv.className = 'textLayer'; const textContent = await page.getTextContent(); pdfjsLib.renderTextLayer({ textContentSource: textContent, container: textLayerDiv, viewport: viewport, textDivs: [], }); pageContentDom.appendChild(textLayerDiv); //add-end: 文本可選則 };
【3】實(shí)現(xiàn)搜索,匹配內(nèi)容高亮,并且可以跳轉(zhuǎn)至匹配內(nèi)容的位置
import { Button } from 'antd'; import { useState, useEffect, useRef } from 'react'; import * as pdfjsLib from 'pdfjs-dist'; // 引入pdfjs-dist const pdfUrl = '/zyk.pdf'; // pdf 文件路徑,pdf文件存放于public目錄下 const workerUrl = `/pdf.worker.min.js`; //webworker存放于public目錄下 pdfjsLib.GlobalWorkerOptions.workerSrc = workerUrl; const viewPdf = (props: {height: string}) => { const {height} = props; const [searchText, setSearchText] = useState(''); const pdfContainerRef = useRef<any>(null); const [pagesList, setPagesList] = useState<any>([]); const [matchList, setMatchList] = useState<any>([]); const scale = 2; // 縮放比例 const createHeightLightCanvas = (viewport: any, pageNumber: number, parentDom: any) => { // 為每頁(yè)創(chuàng)建一個(gè)高亮層canvas const highlightCanvas = document.createElement('canvas'); highlightCanvas.id = `highlightCanvas-${pageNumber}`; highlightCanvas.className = 'highlightCanvas'; highlightCanvas.width = viewport.width; highlightCanvas.height = viewport.height; highlightCanvas.style.position = 'absolute'; highlightCanvas.style.top = '0'; highlightCanvas.style.left = '0'; highlightCanvas.style.zIndex = '1'; parentDom.appendChild(highlightCanvas); } // pageNumber 頁(yè)碼(從0開始) const jumpToPage = (pageIndex: number) => { let beforeCanvasHeight = 0; for (let i = 0; i < pageIndex; i++) { const canvasParentDom = pdfContainerRef.current.querySelector(`#pdfPage-content-${i}`); let canvasParentHeight = canvasParentDom.style.height.replace('px', ''); beforeCanvasHeight += Number(canvasParentHeight); } pdfContainerRef.current.scrollTo({ top: beforeCanvasHeight, // 垂直滾動(dòng)位置 behavior: 'smooth' }); } const getCurrentTextContentY = (canvas: any, match: any) => { // pdfjs 坐標(biāo)系原點(diǎn)在左下角。transform[5]代表y軸的基線,所以需要減去高度 const {textBlock} = match; const { transform, height } = textBlock; return canvas.height - (transform[5] + height -2) * scale; } // 滾動(dòng)到指定的匹配項(xiàng) const scrollToMatch = (match: any) => { const { pageIndex, matchList } = match; const firstMatchContent = matchList[0]; // 獲取滾動(dòng)區(qū)域的高度 const scrollHeight = pdfContainerRef.current.scrollHeight; console.log('滾動(dòng)區(qū)域的高度:', scrollHeight); // 獲取當(dāng)前頁(yè)碼之前dom的高度 let beforePageHeight = 0; for (let i = 0; i < pageIndex; i++) { const canvasParentDom = pdfContainerRef.current.querySelector(`#pdfPage-content-${i}`); let canvasParentHeight = canvasParentDom.style.height.replace('px', ''); beforePageHeight += Number(canvasParentHeight); } // todo 繼續(xù)計(jì)算 匹配項(xiàng)目的高度 const currentPageCanvas = pdfContainerRef.current.querySelector(`#pdfPage-${pageIndex}`); const textContentY = getCurrentTextContentY(currentPageCanvas, firstMatchContent); const offsetTop = 50; //為了滾動(dòng)目標(biāo)文字不頂格 const targetScrollTop = beforePageHeight + textContentY -offsetTop; pdfContainerRef.current.scrollTo({ top: targetScrollTop, // 垂直滾動(dòng)位置 behavior: 'smooth' }); }; // 繪制高亮區(qū)域 const drawHighlights = async (canvas: any, matchesList: MatchBlockItem[]) => { if (matchesList.length === 0) { return; } const context = canvas.getContext('2d'); context.fillStyle = 'rgba(255, 255, 0, 0.5)'; // 黃色半透明填充 matchesList.forEach((match: any) => { const {textBlock} = match; const { transform, width, height, str } = textBlock; // 獲取每一個(gè)字符的寬度 const charWidth = width / str.length; const lightWidth = (match.textEndIndex - match.textStartIndex) * charWidth; const lightHeight = height; const x = transform[4] + match.textStartIndex * charWidth; const y = getCurrentTextContentY(canvas, match); context.fillRect( Math.floor(x * scale), Math.floor(y), Math.ceil(lightWidth * scale), Math.ceil(lightHeight * scale) ); }); }; // 渲染單個(gè)頁(yè)面 const renderPage = async (page: any, pageNumber: number) => { const viewport = page.getViewport({ scale }); const pageContentDom = document.createElement('div'); pageContentDom.id = `pdfPage-content-${pageNumber}`; //為了文本層和canvas層重疊,利用組件庫(kù)的類名 pageContentDom.className = 'pdfViewer'; pageContentDom.style.setProperty('--scale-factor', scale as any); pageContentDom.style.width = `${viewport.width}px`; pageContentDom.style.height = `${viewport.height}px`; pageContentDom.style.position = 'relative'; // 創(chuàng)建 Canvas 元素 const canvas = document.createElement('canvas'); const context = canvas.getContext('2d'); canvas.id=`pdfPage-${pageNumber}` canvas.width = viewport.width; canvas.height = viewport.height; canvas.style.border = '1px solid black'; pageContentDom.appendChild(canvas); createHeightLightCanvas(viewport, pageNumber, pageContentDom); pdfContainerRef.current.appendChild(pageContentDom); // 渲染 PDF 頁(yè)面到 Canvas await page.render({ canvasContext: context, viewport, }).promise; // 渲染文本框 const textLayerDiv = document.createElement('div'); textLayerDiv.style.width = viewport.width; textLayerDiv.style.height = viewport.height; //為了文本層和canvas層重疊,利用組件庫(kù)的類名 textLayerDiv.className = 'textLayer'; const textContent = await page.getTextContent(); pdfjsLib.renderTextLayer({ textContentSource: textContent, container: textLayerDiv, viewport: viewport, textDivs: [], }); pageContentDom.appendChild(textLayerDiv) }; // 渲染 PDF 頁(yè)面 const renderPagesGroup = ( pages: any) => { pages.forEach(({page}:any, index: number) => { renderPage(page, index); }); }; // 加載 PDF 文件 const loadPdf = async (url: any) => { const pdf = await pdfjsLib.getDocument(url).promise; const pages: any[] = []; for (let i = 1; i <= pdf.numPages; i++) { const page = await pdf.getPage(i); const textContent = await page.getTextContent(); pages.push({ page, textContent }); } setPagesList(pages); renderPagesGroup(pages); }; const findAllMatches = (text: string, pattern: string) => { // 創(chuàng)建正則表達(dá)式對(duì)象 const regex = new RegExp(pattern, 'g'); // 使用match方法找到所有匹配項(xiàng) const matches = text.match(regex); // 如果沒有匹配項(xiàng),返回空數(shù)組 if (!matches) { return []; } // 創(chuàng)建一個(gè)數(shù)組來存儲(chǔ)所有匹配的位置 const positions = []; // 遍歷所有匹配項(xiàng),找到它們?cè)谧址械奈恢? let match; while ((match = regex.exec(text)) !== null) { positions.push(match.index); } return positions; } // todo 優(yōu)化參數(shù)個(gè)數(shù), const getMatchesList = ( items: any, currentItem: any, currentItemIndex: number, currentTextIndex: number, searchStr: string): MatchBlockItem[] => { let matchSearchList: MatchBlockItem[] = []; if(currentItem.str.length - (currentTextIndex + 1) < searchStr.length -1 ) { // 獲取當(dāng)前文本塊中剩余字符,如果小于搜索字符長(zhǎng)度,則繼續(xù)查找下一個(gè)文本塊 let itemText = currentItem.str.slice(currentTextIndex); // 獲取當(dāng)前文本塊中剩余字符 let tempMatchSearchList = [{ blockIndex: currentItemIndex, textStartIndex: currentTextIndex, textEndIndex: currentItem.str.length,// 由于統(tǒng)一使用slice截取,所以不包括最后一位 textBlock: currentItem }]; // 存儲(chǔ)后續(xù)文本塊 let index = currentItemIndex; const otherSearchLength = searchStr.length -1; while (itemText.length <= otherSearchLength) { index = index + 1; const currentOtherSearchLength = otherSearchLength - itemText.length; // 當(dāng)前剩余搜索字符長(zhǎng)度 if (items[index].str.length > currentOtherSearchLength) { // 文本塊的長(zhǎng)度大于剩余搜索字符長(zhǎng)度,則截取剩余搜索字符長(zhǎng)度的字符 itemText = `${itemText}${items[index].str.slice(0, currentOtherSearchLength+1)}`; tempMatchSearchList.push({ blockIndex: index, textStartIndex: 0, textEndIndex: currentOtherSearchLength + 1, textBlock: items[index] }) } else { // 文本塊的長(zhǎng)度小于剩余搜索字符長(zhǎng)度,則截取全部字符, 繼續(xù) itemText = `${itemText}${items[index].str}`; tempMatchSearchList.push({ blockIndex: index, textStartIndex: 0, textEndIndex: items[index].str.length, textBlock: items[index] }) } } if (itemText === searchStr) { matchSearchList = matchSearchList.concat(tempMatchSearchList); } } else { // 獲取當(dāng)前文本塊中剩余字符,如果大于等于搜索字符長(zhǎng)度,則截取當(dāng)前文本塊中搜索文本長(zhǎng)度的字符 const textEndIndex = currentTextIndex + searchStr.length; const text = currentItem.str.slice(currentTextIndex, textEndIndex); // 取出匹配字符所在文本塊及后續(xù)文本塊 if (text === searchStr) { console.log('匹配到了:', currentItem, currentItemIndex) matchSearchList.push({ blockIndex: currentItemIndex, textStartIndex: currentTextIndex, textEndIndex: textEndIndex, textBlock: currentItem }) } } return matchSearchList; } // 查找文本的所有出現(xiàn)位置 const findAllOccurrences = (items: any, searchStr: string): MatchBlockItem[] => { const firstSearchStr = searchStr[0]; let matchSearchList: MatchBlockItem[] = []; for(let i=0; i<items.length; i++) { const currentItem = items[i]; const currentMatchIndexList = findAllMatches(currentItem.str, firstSearchStr); // 獲取當(dāng)前文本塊中第一個(gè)匹配字符的索引列表 if (currentMatchIndexList.length > 0) { for(let j=0; j<currentMatchIndexList.length; j++){ matchSearchList = [...matchSearchList, ...getMatchesList(items, currentItem, i, currentMatchIndexList[j], searchStr)]; } } } return matchSearchList; }; const clearHeightLightsCanvas = () => { const highlightCanvases = Array.from(pdfContainerRef.current.querySelectorAll('.highlightCanvas')); highlightCanvases.forEach((canvas: any) => { const context = canvas.getContext('2d'); context.clearRect(0, 0, canvas.width, canvas.height); }); } const handleSearch = async () => { clearHeightLightsCanvas() if (!searchText) { jumpToPage(0); return; } const newMatches: any = []; console.log('pagesList', pagesList) // todo 目前是按照每頁(yè)來匹配,可能會(huì)匹配不到跨頁(yè)的內(nèi)容 pagesList.forEach(async ({textContent}: any, pageIndex: number) => { const pageMatches = findAllOccurrences(textContent.items, searchText); newMatches.push({ pageIndex, // 頁(yè)面索引 matchList: pageMatches, // 匹配項(xiàng)列表 }); }) console.log('newMatches', newMatches); const isNotMatch = newMatches.every((match: any) => match.matchList.length === 0); if (isNotMatch) { alert('未找到匹配項(xiàng)'); return; } /// 重新繪制高亮區(qū)域 pagesList.forEach((_: any, pageIndex: number) => { const highlightCanvas = pdfContainerRef.current.querySelectorAll('.highlightCanvas')[pageIndex]; // 獲取高亮層 Canvas const currentMatches = newMatches.find((match: any) => match.pageIndex === pageIndex); drawHighlights( highlightCanvas, currentMatches?.matchList || [] ); }); // 跳轉(zhuǎn) const isExistItem = newMatches.find((match: any) => match.matchList.length > 0); if (isExistItem) { scrollToMatch(isExistItem); } }; // 初始化 PDF.js useEffect(() => { loadPdf(pdfUrl); }, []); return <> <div> <h1>PDF 搜索(基于@pdfjs-dist-自定義實(shí)現(xiàn))</h1> <input type="text" value={searchText} onChange={(e) => setSearchText(e.target.value)} placeholder="輸入要搜索的內(nèi)容" /> <Button onClick={handleSearch}>搜索</Button> <div> <div style={{ height: height || '500px' }}> {/* PDF 容器 */} <div ref={pdfContainerRef} style={{ position: 'relative', height: '100%', overflowY: 'scroll' }} /> </div> </div> </div> </> }; export default viewPdf;
【4】獲取pdf文件中目錄的數(shù)據(jù)結(jié)構(gòu)
.... //基于‘【1】實(shí)現(xiàn)pdf預(yù)覽'的代碼 const get= async (url: any) => { const pdf = await pdfjsLib.getDocument(url).promise; // 獲取目錄數(shù)據(jù) const pdfCatalogue= await pdf.getOutline(); console.log('目錄數(shù)據(jù):', pdfCatalogue); }; ...
總結(jié)
到此這篇關(guān)于前端使用pdf.js渲染pdf文件解決方案的文章就介紹到這了,更多相關(guān)前端pdf.js渲染pdf文件內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
javascript實(shí)現(xiàn)tabs選項(xiàng)卡切換效果(擴(kuò)展版)
常用的頁(yè)面效果有彈出層效果,無縫滾動(dòng)效果,選項(xiàng)卡切換效果,接下來與大家分享一款自己用原生javascript寫的選項(xiàng)卡切換效果在原有的基礎(chǔ)上進(jìn)行了擴(kuò)展,加入了自動(dòng)輪播,這樣就變成了類似圖片輪播的效果2013-03-03React?diff算法面試考點(diǎn)超詳細(xì)講解
渲染真實(shí)DOM的開銷很大,有時(shí)候我們修改了某個(gè)數(shù)據(jù),直接渲染到真實(shí)dom上會(huì)引起整個(gè)dom樹的重繪和重排。我們希望只更新我們修改的那一小塊dom,而不是整個(gè)dom,diff算法就幫我們實(shí)現(xiàn)了這點(diǎn)。diff算法的本質(zhì)就是:找出兩個(gè)對(duì)象之間的差異,目的是盡可能做到節(jié)點(diǎn)復(fù)用2022-12-12JavaScript?canvas繪制動(dòng)態(tài)圓環(huán)進(jìn)度條
這篇文章主要為大家詳細(xì)介紹了JavaScript?canvas繪制動(dòng)態(tài)圓環(huán)進(jìn)度條,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-06-06JavaScript?內(nèi)置對(duì)象?BigInt詳細(xì)解析
這篇文章主要介紹了JavaScript?內(nèi)置對(duì)象?BigInt詳細(xì)解析,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-07-07JavaScript實(shí)現(xiàn)的購(gòu)物車效果可以運(yùn)用在好多地方
JavaScript實(shí)現(xiàn)的購(gòu)物車效果,當(dāng)然這個(gè)效果可以運(yùn)用在好多地方,比如好友的選擇,人力資源模塊等等,需要的朋友可以參考下2014-05-05基于BootStrap Metronic開發(fā)框架經(jīng)驗(yàn)小結(jié)【六】對(duì)話框及提示框的處理和優(yōu)化
這篇文章主要介紹了基于BootStrap Metronic開發(fā)框架經(jīng)驗(yàn)小結(jié)【六】對(duì)話框及提示框的處理和優(yōu)化的相關(guān)知識(shí),主要對(duì)比說明在Bootstrap開發(fā)中用到的這些技術(shù)要點(diǎn),對(duì)此文感興趣的朋友一起學(xué)習(xí)吧2016-05-05微信小程序云開發(fā)實(shí)現(xiàn)數(shù)據(jù)添加、查詢和分頁(yè)
這篇文章主要為大家詳細(xì)介紹了微信小程序云開發(fā)實(shí)現(xiàn)數(shù)據(jù)添加、查詢和分頁(yè),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-05-05使用webpack/gulp構(gòu)建TypeScript項(xiàng)目的方法示例
這篇文章主要介紹了使用webpack/gulp構(gòu)建TypeScript項(xiàng)目的方法示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12