JS前端輕松導(dǎo)出Excel的通用方法詳解
背景
在項(xiàng)目開(kāi)發(fā)中,我們經(jīng)常遇到需要將數(shù)據(jù)導(dǎo)出為Excel文件的情況。然而,這個(gè)過(guò)程可能相對(duì)復(fù)雜和繁瑣,特別是對(duì)于大量數(shù)據(jù)和復(fù)雜表格結(jié)構(gòu)的情況。因此,我們需要一個(gè)通用方法,能夠在各種場(chǎng)景下靈活使用,實(shí)現(xiàn)簡(jiǎn)單高效的數(shù)據(jù)導(dǎo)出操作
安裝依賴:
npm install file-saver --save npm install script-loader --save-dev npm install xlsx --save
如果項(xiàng)目用的不是 npm 用 yarn 或 pnpm 一樣的操作
在src下創(chuàng)建目錄 vendor
目錄下創(chuàng)建兩個(gè)文件
Blob.js
/* eslint-disable */ (function (view) { "use strict"; view.URL = view.URL || view.webkitURL; if (view.Blob && view.URL) { try { new Blob; return; } catch (e) { } } // Internally we use a BlobBuilder implementation to base Blob off of // in order to support older browsers that only have BlobBuilder var BlobBuilder = view.BlobBuilder || view.WebKitBlobBuilder || view.MozBlobBuilder || (function (view) { var get_class = function (object) { return Object.prototype.toString.call(object).match(/^[object\s(.*)]$/)[1]; } , FakeBlobBuilder = function BlobBuilder() { this.data = []; } , FakeBlob = function Blob(data, type, encoding) { this.data = data; this.size = data.length; this.type = type; this.encoding = encoding; } , FBB_proto = FakeBlobBuilder.prototype , FB_proto = FakeBlob.prototype , FileReaderSync = view.FileReaderSync , FileException = function (type) { this.code = this[this.name = type]; } , file_ex_codes = ( "NOT_FOUND_ERR SECURITY_ERR ABORT_ERR NOT_READABLE_ERR ENCODING_ERR " + "NO_MODIFICATION_ALLOWED_ERR INVALID_STATE_ERR SYNTAX_ERR" ).split(" ") , file_ex_code = file_ex_codes.length , real_URL = view.URL || view.webkitURL || view , real_create_object_URL = real_URL.createObjectURL , real_revoke_object_URL = real_URL.revokeObjectURL , URL = real_URL , btoa = view.btoa , atob = view.atob , ArrayBuffer = view.ArrayBuffer , Uint8Array = view.Uint8Array ; FakeBlob.fake = FB_proto.fake = true; while (file_ex_code--) { FileException.prototype[file_ex_codes[file_ex_code]] = file_ex_code + 1; } if (!real_URL.createObjectURL) { URL = view.URL = {}; } URL.createObjectURL = function (blob) { var type = blob.type , data_URI_header ; if (type === null) { type = "application/octet-stream"; } if (blob instanceof FakeBlob) { data_URI_header = "data:" + type; if (blob.encoding === "base64") { return data_URI_header + ";base64," + blob.data; } else if (blob.encoding === "URI") { return data_URI_header + "," + decodeURIComponent(blob.data); } if (btoa) { return data_URI_header + ";base64," + btoa(blob.data); } else { return data_URI_header + "," + encodeURIComponent(blob.data); } } else if (real_create_object_URL) { return real_create_object_URL.call(real_URL, blob); } }; URL.revokeObjectURL = function (object_URL) { if (object_URL.substring(0, 5) !== "data:" && real_revoke_object_URL) { real_revoke_object_URL.call(real_URL, object_URL); } }; FBB_proto.append = function (data/*, endings*/) { var bb = this.data; // decode data to a binary string if (Uint8Array && (data instanceof ArrayBuffer || data instanceof Uint8Array)) { var str = "" , buf = new Uint8Array(data) , i = 0 , buf_len = buf.length ; for (; i < buf_len; i++) { str += String.fromCharCode(buf[i]); } bb.push(str); } else if (get_class(data) === "Blob" || get_class(data) === "File") { if (FileReaderSync) { var fr = new FileReaderSync; bb.push(fr.readAsBinaryString(data)); } else { // async FileReader won't work as BlobBuilder is sync throw new FileException("NOT_READABLE_ERR"); } } else if (data instanceof FakeBlob) { if (data.encoding === "base64" && atob) { bb.push(atob(data.data)); } else if (data.encoding === "URI") { bb.push(decodeURIComponent(data.data)); } else if (data.encoding === "raw") { bb.push(data.data); } } else { if (typeof data !== "string") { data += ""; // convert unsupported types to strings } // decode UTF-16 to binary string bb.push(unescape(encodeURIComponent(data))); } }; FBB_proto.getBlob = function (type) { if (!arguments.length) { type = null; } return new FakeBlob(this.data.join(""), type, "raw"); }; FBB_proto.toString = function () { return "[object BlobBuilder]"; }; FB_proto.slice = function (start, end, type) { var args = arguments.length; if (args < 3) { type = null; } return new FakeBlob( this.data.slice(start, args > 1 ? end : this.data.length) , type , this.encoding ); }; FB_proto.toString = function () { return "[object Blob]"; }; FB_proto.close = function () { this.size = this.data.length = 0; }; return FakeBlobBuilder; }(view)); view.Blob = function Blob(blobParts, options) { var type = options ? (options.type || "") : ""; var builder = new BlobBuilder(); if (blobParts) { for (var i = 0, len = blobParts.length; i < len; i++) { builder.append(blobParts[i]); } } return builder.getBlob(type); }; }(typeof self !== "undefined" && self || typeof window !== "undefined" && window || this.content || this));
Export2Excel.js
/* eslint-disable */ import {saveAs} from 'file-saver' import * as XLSX from 'xlsx' import XLSXS from "xlsx-style" require('script-loader!file-saver'); require('./Blob');//這里是你的Blob.js的地址 require('script-loader!xlsx/dist/xlsx.core.min'); /** * * @param {*} table * @returns * 將HTML表格轉(zhuǎn)換為二維數(shù)組 * 函數(shù)支持處理表格中的單元格合并、行列跨度以及特定數(shù)據(jù)類型轉(zhuǎn)換 */ function generateArray(table) { let out = []; let rows = table.querySelectorAll('tr'); let ranges = []; for (let R = 0; R < rows.length; ++R) { let outRow = []; let row = rows[R]; let columns = row.querySelectorAll('td'); for (let C = 0; C < columns.length; ++C) { let cell = columns[C]; let colspan = cell.getAttribute('colspan'); let rowspan = cell.getAttribute('rowspan'); let cellValue = cell.innerText; if (cellValue !== "" && cellValue == +cellValue) cellValue = +cellValue; //Skip ranges ranges.forEach(function (range) { if (R >= range.s.r && R <= range.e.r && outRow.length >= range.s.c && outRow.length <= range.e .c) { for (let i = 0; i <= range.e.c - range.s.c; ++i) outRow.push(null); } }); //Handle Row Span if (rowspan || colspan) { rowspan = rowspan || 1; colspan = colspan || 1; ranges.push({ s: { r: R, c: outRow.length }, e: { r: R + rowspan - 1, c: outRow.length + colspan - 1 } }); } ; //Handle Value outRow.push(cellValue !== "" ? cellValue : null); //Handle Colspan if (colspan) for (let k = 0; k < colspan - 1; ++k) outRow.push(null); } out.push(outRow); } return [out, ranges]; }; function datenum(v, date1904) { if (date1904) v += 1462; let epoch = Date.parse(v); return (epoch - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000); } /** * * @param {*} data * @param {*} opts * @returns * 用于將二維數(shù)組轉(zhuǎn)換為Excel工作表對(duì)象的JavaScript函數(shù) * 使用了XLSX.js庫(kù)提供的工具方法來(lái)編碼單元格引用和工作表范圍,可生成可供Excel處理的工作表數(shù)據(jù) * 支持將一些數(shù)據(jù)類型轉(zhuǎn)換為Excel支持的數(shù)據(jù)類型,例如將日期對(duì)象轉(zhuǎn)換為數(shù)字類型,并設(shè)置相應(yīng)的格式。 */ function sheet_from_array_of_arrays(data, opts) { let ws = {}; let range = { s: { c: 10000000, r: 10000000 }, e: { c: 0, r: 0 } }; for (let R = 0; R != data.length; ++R) { for (let C = 0; C != data[R].length; ++C) { if (range.s.r > R) range.s.r = R; if (range.s.c > C) range.s.c = C; if (range.e.r < R) range.e.r = R; if (range.e.c < C) range.e.c = C; let cell = { v: data[R][C] }; if (cell.v == null) continue; let cell_ref = XLSX.utils.encode_cell({ c: C, r: R }); if (typeof cell.v === 'number') cell.t = 'n'; else if (typeof cell.v === 'boolean') cell.t = 'b'; else if (cell.v instanceof Date) { cell.t = 'n'; cell.z = XLSX.SSF._table[14]; cell.v = datenum(cell.v); } else cell.t = 's'; ws[cell_ref] = cell; } } if (range.s.c < 10000000) ws['!ref'] = XLSX.utils.encode_range(range); return ws; } function Workbook() { if (!(this instanceof Workbook)) return new Workbook(); this.SheetNames = []; this.Sheets = {}; } function s2ab(s) { let buf = new ArrayBuffer(s.length); let view = new Uint8Array(buf); for (let i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF; return buf; } export function export_table_to_excel(id) { let theTable = document.getElementById(id); let oo = generateArray(theTable); let ranges = oo[1]; /* original data */ let data = oo[0]; let ws_name = "SheetJS"; let wb = new Workbook(), ws = sheet_from_array_of_arrays(data); /* add ranges to worksheet */ // ws['!cols'] = ['apple', 'banan']; ws['!merges'] = ranges; /* add worksheet to workbook */ wb.SheetNames.push(ws_name); wb.Sheets[ws_name] = ws; let wbout = XLSX.write(wb, { bookType: 'xlsx', bookSST: false, type: 'binary' }); saveAs(new Blob([s2ab(wbout)], { type: "application/octet-stream" }), "test.xlsx") } export function export_json_to_excelhb({ multiHeader = [], // 第一行表頭 multiHeader2 = [], // 第二行表頭 header = [], // 第三行表頭 data,//傳遞的數(shù)據(jù) filename, //文件名 merges = [], // 合并 autoWidth = true,//用于設(shè)置列寬的 bookType = 'xlsx' } = {}) { /* original data */ filename = filename || '列表'; data = [...data] for (let i = header.length - 1; i > -1; i--) { data.unshift(header[i]) } for (let i = multiHeader2.length - 1; i > -1; i--) { data.unshift(multiHeader2[i]) } for (let i = multiHeader.length - 1; i > -1; i--) { data.unshift(multiHeader[i]) } let ws_name = "SheetJS"; let wb = new Workbook(), ws = sheet_from_array_of_arrays(data); // 設(shè)置單元格公共樣式 let borderAll = { //單元格外側(cè)框線 top: { style: 'thin', }, bottom: { style: 'thin', }, left: { style: 'thin', }, right: { style: 'thin', } }; for (let key in ws) { // 單元格公共樣式設(shè)置 if (ws[key] instanceof Object) { ws[key].s = { border: borderAll, alignment: { horizontal: 'center', //水平居中對(duì)齊 vertical: 'center',//垂直居中 wrapText: 1,//自動(dòng)換行 }, // fill: { //背景色 // fgColor: { rgb: 'dbf3f2' } // }, font: { sz: 10,//單元格中字體的樣式與顏色設(shè)置 color: { rgb: '000000' } }, bold: true, numFmt: 0 } } //給特定格子(帶'1'的,即首行 標(biāo)題)添加樣式,下面同理 // if (key.replace(/[^0-9]/ig, '') === '1') { // ws[key].s = { // ...ws[key].s, // fill: { //背景色 // fgColor: { rgb: 'd4e6fd' } // }, // font: {//覆蓋字體 // name: '等線', // sz: 10, // // bold: true // }, // } // } if (key === 'A1') { ws[key].s = { ...ws[key].s, fill: { //背景色 fgColor: {rgb: 'd4e6fd'} }, } } // if (key === 'B2' || key === 'C2' || key === 'D2' || key === 'E2' || key === 'F2' || key === 'G2') { // ws[key].s = { // ...ws[key].s, // fill: { //背景色 // fgColor: { rgb: 'fbedd7' } // } // } // } } if (merges.length > 0) { if (!ws['!merges']) ws['!merges'] = []; merges.forEach(item => { ws['!merges'].push(XLSX.utils.decode_range( XLSX.utils.encode_cell(item.s) + ':' + XLSX.utils.encode_cell(item.e) )); // 設(shè)置單元格的樣式 ws[XLSX.utils.encode_cell(item.s)].s = item.style; }); } // ws['I2'] = ws['H2'] = ws['G2'] = ws['F2'] = ws['E2'] = ws['D2'] = ws['C2'] = ws['B2'] = ws['A2']//用于第二行的單元格的樣式設(shè)置(如果是合并的第一行,就是1) // if (merges.length > 0) { // if (!ws['!merges']) ws['!merges'] = []; // merges.forEach(item => { // console.log(item); // ws['!merges'].push(XLSX.utils.decode_range(item)) // }) // } if (autoWidth) { let colWidths = []; // 計(jì)算每一列的所有單元格寬度 // 先遍歷行 data.forEach((row) => { // 列序號(hào) let index = 0 // 遍歷列 for (const key in row) { if (colWidths[index] == null) colWidths[index] = [] switch (typeof row[key]) { case 'string': case 'number': case 'boolean': colWidths[index].push(getCellWidth(row[key])) break case 'object': case 'function': colWidths[index].push(0) break } index++ } }) ws['!cols'] = []; // 第三行表頭的設(shè)置 colWidths.forEach((widths, index) => { // 計(jì)算列頭的寬度 // widths.push(getCellWidth(header[index])) // 設(shè)置最大值為列寬 ws['!cols'].push({ wch: Math.max(...widths) }) }) } /* add worksheet to workbook */ wb.SheetNames.push(ws_name); wb.Sheets[ws_name] = ws; let wbout = XLSXS.write(wb, { bookType: bookType, bookSST: false, type: 'binary' }); saveAs(new Blob([s2ab(wbout)], { type: "application/octet-stream" }), `${filename}.${bookType}`); } export function getCellWidth(value) { if (value == null) { return 10 } else if (value.toString().charCodeAt(0) > 255) { // 判斷是否包含中文 let length = value.toString().length * 2 if (length > 60) { length = length - 40 //這里的寬度可以自己設(shè)定,在前面設(shè)置wrapText: 1可以在單元格內(nèi)換行 } return length } else { return value.toString().length * 1.2 } }
import {saveAs} from 'file-saver' import * as XLSX from 'xlsx' import XLSXS from "xlsx-style" `file-saver`庫(kù)中導(dǎo)入`saveAs`函數(shù),用于保存文件。 `xlsx`庫(kù)中導(dǎo)入所有的功能,并將其作為`XLSX`對(duì)象進(jìn)行引用。 `xlsx-style`庫(kù)導(dǎo)入是支持樣式的,并將其命名為`XLSXS`。
require('script-loader!file-saver'); require('./Blob');//這里是你的Blob.js的地址 require('script-loader!xlsx/dist/xlsx.core.min'); 這部分代碼使用`require`和`script-loader`來(lái)加載和引入所需的腳本文件。 其中,通過(guò)`script-loader!file-saver`來(lái)加載`file-saver`庫(kù)所需要的腳本, `require('./Blob')`用于加載自定義的`Blob.js`文件, `script-loader!xlsx/dist/xlsx.core.min`用于加載`xlsx`庫(kù)的核心腳本。
上述代碼片段中的具體路徑和文件名可能因你的項(xiàng)目結(jié)構(gòu)而有所不同,你需要根據(jù)實(shí)際情況進(jìn)行調(diào)整。
通過(guò)以上步驟,你可以在項(xiàng)目中使用saveAs()
函數(shù)將文件保存到本地,以及使用XLSX
和XLSXS
對(duì)象進(jìn)行 Excel 文件的處理和操作。
代碼實(shí)現(xiàn)
Blob.js 和Export2Excel.js 兩個(gè)文件網(wǎng)上有很多,根據(jù)個(gè)人項(xiàng)目有細(xì)微不同,基本大同小異, 但是在項(xiàng)目有非常多的數(shù)據(jù)圖表以及各種不同類型的表格。不做封裝通用方法,代碼重復(fù)量便會(huì)增多并且不易維護(hù)
因此,我基于Export2Excel中做了一個(gè)通用方法,代碼如下:
創(chuàng)建 vendor/exportExcel.js 文件
// 導(dǎo)出Excel通用方法 async function exportExcel(multiHeader, multiHeader2, filterVal, tableData, tabulationTitle, indexNumber) { // 導(dǎo)出標(biāo)題 const EXPORT_FILENAME = tabulationTitle; // 單元格樣式1 dbf3f2 const style1 = { fill: {patternType: 'solid', fgColor: {rgb: 'dbf3f2'}}, border: {top: {style: 'thin'}, left: {style: 'thin'}, bottom: {style: 'thin'}, right: {style: 'thin'}}, alignment: {horizontal: 'center', vertical: 'center', wrapText: 1} }; // 單元格樣式2 fbedd7 const style2 = { fill: {patternType: 'solid', fgColor: {rgb: 'fbedd7'}}, border: {top: {style: 'thin'}, left: {style: 'thin'}, bottom: {style: 'thin'}, right: {style: 'thin'}}, alignment: {horizontal: 'center', vertical: 'center', wrapText: 1} }; // 進(jìn)行所有表頭的單元格合并 let headerList = []; let titleList = []; let styleNumber = 1; // 初始為1 1級(jí)表頭樣式判斷 let headerStyle = style2; let rowIndex = 1; let colIndex = 1; let isOdd = true; let count = 0; const indexNumber2 = indexNumber + 1 // 一級(jí)表頭合并與顏色賦值 if (multiHeader[0] && multiHeader[0].length > 0) { multiHeader[0].forEach((group, index) => { if (rowIndex < multiHeader[0].length) { headerList.push({s: {r: 0, c: rowIndex}, e: {r: 0, c: rowIndex + indexNumber}, style: headerStyle}); rowIndex += indexNumber2; } // 修改樣式值 if (styleNumber === 1) { styleNumber = 2 // 修改樣式值 headerStyle = style1 } else { styleNumber = 1 // 修改樣式值 headerStyle = style2 } }); } if (multiHeader2[0] && multiHeader2[0].length > 0) { multiHeader2[0].forEach((group, index) => { if (colIndex < multiHeader2[0].length) { titleList.push({s: {r: 1, c: colIndex}, e: {r: 1, c: colIndex}, style: isOdd ? style2 : style1}); colIndex += 1; count++; } if (count % indexNumber2 === 0) { isOdd = !isOdd; } }); } // 進(jìn)行所有表頭的單元格合并 const merges = [ // 將A1和A2合并,并設(shè)置背景色1 { s: {r: 0, c: 0}, e: {r: 1, c: 0}, style: { fill: {patternType: 'solid', fgColor: {rgb: 'd4e6fd'}}, border: {top: {style: 'thin'}, left: {style: 'thin'}, bottom: {style: 'thin'}, right: {style: 'thin'}}, alignment: {horizontal: 'center', vertical: 'center', wrapText: 1} } }, ...headerList, ...titleList // 二級(jí)表頭 ]; const data = tableData.map((v) => filterVal.map((j) => v[j])); await import('/src/vendor/Export2Excel.js').then((excel) => { excel.export_json_to_excelhb({ multiHeader, // 這里是第一行的表頭 multiHeader2, // 這里是第二行的表頭 data, filename: EXPORT_FILENAME, merges }); }); } export default {exportExcel}
上面給出的代碼是一個(gè)導(dǎo)出Excel通用方法的示例。讓我們逐步解析其中的關(guān)鍵邏輯:
- 首先,在函數(shù)的參數(shù)中,我們傳入了一些必要的參數(shù),如多級(jí)表頭(
multiHeader
和multiHeader2
)、數(shù)據(jù)過(guò)濾列(filterVal
)、表格數(shù)據(jù)(tableData
)、表格標(biāo)題(tabulationTitle
)和索引號(hào)(indexNumber
)。這些參數(shù)會(huì)作為函數(shù)的輸入,用于生成Excel文件。 - 接下來(lái),我們定義了一些常量,如導(dǎo)出文件名稱(
EXPORT_FILENAME
)和兩種不同樣式的單元格樣式(style1
和style2
)。這些樣式可以用于美化導(dǎo)出的Excel表格,使其更加具有可讀性和專業(yè)感。 - 在進(jìn)行表頭的合并和樣式設(shè)置之后,我們使用
import
語(yǔ)法引入了名為Export2Excel.js
的庫(kù)文件。這個(gè)庫(kù)文件是用于實(shí)現(xiàn)數(shù)據(jù)導(dǎo)出到Excel的核心工具,我們可以調(diào)用其中的export_json_to_excelhb
方法來(lái)實(shí)現(xiàn)導(dǎo)出功能。 - 最后,我們將整理好的數(shù)據(jù)、需要導(dǎo)出的文件名、表頭樣式和單元格合并信息作為參數(shù),調(diào)用
export_json_to_excelhb
方法實(shí)現(xiàn)導(dǎo)出操作。
導(dǎo)出示例
main.js引入全局
import Excel from './vendor/exportExcel' Vue.prototype.$Excel = Excel;
// 調(diào)用導(dǎo)出Excel通用方法 async exportExcelDemo() { let multiHeader = []; // 第一行的表頭 let multiHeader2 = [] // 第二行的表頭 let filterVal = [] // 對(duì)應(yīng)字段 multiHeader = [[this.areaNameText, '游戲廳', '', '', '游戲廳', '', '', '網(wǎng)咖', '', '',]] multiHeader2 = [['', this.current, this.text, '同比去年', this.current, this.text, '同比去年', this.current, this.text, '同比去年',]]; filterVal = ['areaName', 'sumValue03', 'momPerCent', 'yoyPerCent', 'sumValue032', 'momPerCent2', 'yoyPerCent2', 'sumValue033', 'momPerCent3', 'yoyPerCent3', ]; const tabulationTitle = '導(dǎo)出表格'; // 導(dǎo)出標(biāo)題 const indexNumber = this.activeDate === 'day' ? 6 : 7 // 根據(jù)業(yè)務(wù)需求進(jìn)行調(diào)整 需要的字段數(shù)據(jù)也不同 await this.$Excel.exportExcel(multiHeader, multiHeader2, filterVal, tableData, tabulationTitle,indexNumber); } // 調(diào)用示例 this.exportExcelDemo();
應(yīng)用場(chǎng)景
這個(gè)通用的導(dǎo)出Excel方法適用于多種前端開(kāi)發(fā)場(chǎng)景,比如:
- 在數(shù)據(jù)可視化項(xiàng)目中,將圖表數(shù)據(jù)導(dǎo)出為Excel文件,以便進(jìn)一步分析和處理。
- 在數(shù)據(jù)管理系統(tǒng)中,將查詢到的數(shù)據(jù)導(dǎo)出為Excel表格,方便用戶離線使用和分享。
- 在電子商務(wù)平臺(tái)中,將商品信息、銷售報(bào)表等導(dǎo)出為Excel文件,方便商家進(jìn)行業(yè)務(wù)分析和統(tǒng)計(jì)。
總結(jié)
通過(guò)這個(gè)通用的導(dǎo)出Excel方法,我們能夠輕松地處理各種數(shù)據(jù)導(dǎo)出需求,并且能夠適應(yīng)不同的表格結(jié)構(gòu)和數(shù)據(jù)量。只需簡(jiǎn)單配置參數(shù),就能生成美觀、便于閱讀和處理的Excel文件。
希望這篇文章能夠?qū)Υ蠹以谇岸碎_(kāi)發(fā)中的數(shù)據(jù)導(dǎo)出需求有所幫助。
以上就是JS前端輕松導(dǎo)出Excel的通用方法詳解的詳細(xì)內(nèi)容,更多關(guān)于JS導(dǎo)出Excel通用方法的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JavaScript獲取GridView中用戶點(diǎn)擊控件的行號(hào),列號(hào)
GridView中的某幾列有按鈕,需要獲取用戶當(dāng)前點(diǎn)的按鈕的行號(hào)(捎帶的得到列號(hào))2009-04-04Javascript setInterval的兩種調(diào)用方法(實(shí)例講解)
這篇文章主要是對(duì)Javascript setInterval的兩種調(diào)用方法解析了詳細(xì)的分析介紹,需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所幫助2013-11-11javascript實(shí)現(xiàn)圣旨卷軸展開(kāi)效果(代碼分享)
本文主要介紹了javascript實(shí)現(xiàn)圣旨卷軸展開(kāi)效果的示例代碼。具有很好的參考價(jià)值。下面跟著小編一起來(lái)看下吧2017-03-03