您现在的位置是:网站首页 > 服务端渲染方案文章详情

服务端渲染方案

服务端渲染方案

ECharts 作为一款强大的数据可视化库,通常运行在浏览器环境中。但在某些场景下,比如需要 SEO 优化、首屏加载速度提升或静态内容生成时,服务端渲染(SSR)成为必要选择。ECharts 提供了多种服务端渲染方案,能够满足不同技术栈的需求。

基于 Node.js 的渲染方案

Node.js 环境下可以通过 node-canvasecharts-node 实现服务端渲染。核心思路是模拟浏览器环境,生成图表图像或 SVG 字符串。

const echarts = require('echarts');
const { createCanvas } = require('canvas');

// 创建虚拟 canvas
const canvas = createCanvas(800, 600);
const chart = echarts.init(canvas);

// 设置配置项
chart.setOption({
  title: { text: '服务端渲染示例' },
  series: [{
    type: 'bar',
    data: [12, 19, 3, 5, 2, 3]
  }]
});

// 获取 PNG 图像
const buffer = canvas.toBuffer('image/png');
require('fs').writeFileSync('output.png', buffer);

这种方案需要注意字体问题,中文可能需要额外配置:

const { registerFont } = require('canvas');
registerFont('path/to/simhei.ttf', { family: 'SimHei' });

// 在 option 中指定字体
chart.setOption({
  textStyle: {
    fontFamily: 'SimHei'
  }
});

基于 Puppeteer 的无头浏览器方案

对于复杂图表或需要精确渲染的场景,可以使用 Puppeteer 控制真实浏览器进行渲染:

const puppeteer = require('puppeteer');
const fs = require('fs');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  
  // 加载本地 HTML 模板
  await page.setContent(`
    <!DOCTYPE html>
    <div id="chart" style="width:800px;height:600px;"></div>
    <script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
    <script>
      const chart = echarts.init(document.getElementById('chart'));
      chart.setOption(${JSON.stringify({
        series: [{ type: 'pie', data: [{value:335, name:'直接访问'}] }]
      })});
    </script>
  `);

  // 等待渲染完成
  await page.waitForTimeout(500);
  
  // 截图保存
  const element = await page.$('#chart');
  await element.screenshot({ path: 'chart.png' });
  
  await browser.close();
})();

服务端 SVG 生成方案

ECharts 5+ 版本支持直接导出 SVG:

const echarts = require('echarts');

// 初始化虚拟节点
const container = document.createElement('div');
const chart = echarts.init(container, null, {
  renderer: 'svg',
  width: 800,
  height: 600
});

chart.setOption({
  xAxis: { type: 'category', data: ['Mon', 'Tue'] },
  yAxis: { type: 'value' },
  series: [{ data: [820, 932], type: 'line' }]
});

// 获取 SVG 字符串
const svgStr = chart.renderToSVGString();

与前端框架的集成方案

Next.js 集成示例

在 Next.js 的 getServerSideProps 中渲染图表:

export async function getServerSideProps() {
  const { createCanvas } = require('canvas');
  const echarts = require('echarts');
  
  const canvas = createCanvas(800, 600);
  const chart = echarts.init(canvas);
  
  chart.setOption({
    series: [{
      type: 'scatter',
      data: [[10, 5], [20, 20]]
    }]
  });

  return {
    props: {
      chartData: canvas.toDataURL()
    }
  };
}

// 页面组件中使用
function Page({ chartData }) {
  return <img src={chartData} alt="服务端渲染图表" />;
}

Nuxt.js 集成方案

创建 Nuxt 服务器中间件:

// server/middleware/chart.js
const { createCanvas } = require('canvas');
const echarts = require('echarts');

export default function(req, res) {
  const canvas = createCanvas(400, 300);
  const chart = echarts.init(canvas);
  
  chart.setOption({
    series: [{ type: 'bar', data: [5, 20, 36, 10] }]
  });

  res.setHeader('Content-Type', 'image/png');
  res.end(canvas.toBuffer());
}

性能优化策略

  1. 缓存渲染结果:对相同配置的图表进行缓存
const chartCache = new Map();

function renderChart(options) {
  const cacheKey = JSON.stringify(options);
  if (chartCache.has(cacheKey)) {
    return chartCache.get(cacheKey);
  }
  
  // ...渲染逻辑
  chartCache.set(cacheKey, result);
  return result;
}
  1. 使用 Worker 线程:避免阻塞主线程
const { Worker } = require('worker_threads');

function renderInWorker(options) {
  return new Promise((resolve) => {
    const worker = new Worker(`
      const { parentPort } = require('worker_threads');
      const echarts = require('echarts');
      const { createCanvas } = require('canvas');
      
      parentPort.on('message', (options) => {
        const canvas = createCanvas(800, 600);
        const chart = echarts.init(canvas);
        chart.setOption(options);
        parentPort.postMessage(canvas.toBuffer());
      });
    `, { eval: true });
    
    worker.on('message', resolve);
    worker.postMessage(options);
  });
}
  1. 按需加载 ECharts 组件
// 只加载需要的图表组件
const echarts = require('echarts/core');
const { BarChart } = require('echarts/charts');
const { CanvasRenderer } = require('echarts/renderers');

echarts.use([BarChart, CanvasRenderer]);

常见问题解决方案

字体显示异常:确保服务端有相应字体文件

// 使用系统字体或注册自定义字体
const { registerFont } = require('canvas');
registerFont('/path/to/font.ttf', { family: 'Custom Font' });

图表交互问题:服务端渲染后保留交互能力

<div id="chart-container">
  <!-- 服务端渲染的占位图 -->
  <img src="/chart.png" alt="预览图" />
</div>

<script>
  // 客户端水合
  fetch('/chart-config.json')
    .then(res => res.json())
    .then(option => {
      const chart = echarts.init(document.getElementById('chart-container'));
      chart.setOption(option);
    });
</script>

大图表内存问题:分批渲染超大图表

function renderLargeChart(options) {
  const partialOptions = {
    ...options,
    series: options.series.slice(0, 10) // 先渲染部分数据
  };
  
  // 后续通过 WebSocket 或 AJAX 加载剩余数据
}

高级应用场景

PDF 报表生成:将 ECharts 渲染到 PDF 文档

const { PDFDocument } = require('pdf-lib');
const fs = require('fs');

async function generatePDF() {
  const pdfDoc = await PDFDocument.create();
  const page = pdfDoc.addPage([800, 600]);
  
  // 渲染图表到 PNG
  const pngImage = await pdfDoc.embedPng(chartBuffer);
  page.drawImage(pngImage, { x: 0, y: 0 });
  
  fs.writeFileSync('report.pdf', await pdfDoc.save());
}

邮件嵌入图表:直接将 SVG 嵌入 HTML 邮件

const svgString = chart.renderToSVGString();

const emailHtml = `
  <div>
    <h1>每日报表</h1>
    ${svgString}
  </div>
`;

命令行输出:在终端显示图表

const { termImage } = require('term-img');

function renderInTerminal() {
  const buffer = chartCanvas.toBuffer();
  console.log(termImage(buffer, { width: '50%' }));
}

我的名片

网名:~川~

岗位:console.log 调试员

坐标:重庆市-九龙坡区

邮箱:cc@qdcc.cn

沙漏人生

站点信息

  • 建站时间:2013/03/16
  • 本站运行
  • 文章数量
  • 总访问量
微信公众号
每次关注
都是向财富自由迈进的一步