Renderizar HTML para uma imagem

166

Existe uma maneira de renderizar html para uma imagem como PNG? Eu sei que isso é possível com canvas, mas eu gostaria de renderizar o elemento html padrão como div, por exemplo.

Martin Delille
fonte
Para criar algum tipo de assistência ao usuário, por exemplo. Infelizmente, isso não é possível (por razões de segurança?). Você precisa pedir ao usuário para pressionar PrintScreen para fazer alguma coisa.
MaxArt
1
possível duplicata de Como converter HTML de um site em uma imagem?
Ernest Friedman-Hill
Eu quero usar HTML / CSS para criar um logotipo.
Martin Delille
4
@ ErnestFriedman-Hill não é uma duplicata: a pergunta que você mencionou é específica para java.
Martin Delille
Aqui está um tutorial passo a passo para a conversão de HTML para IMAGE, formato png ou jpeg codepedia.info/2016/02/…
Satinder singh

Respostas:

41

Sei que essa é uma pergunta bastante antiga, que já tem muitas respostas, mas ainda passei horas tentando realmente fazer o que queria:

  • com um arquivo html, gere uma imagem (png) com fundo transparente na linha de comando

Usando o Chrome sem cabeça (versão 74.0.3729.157 a partir desta resposta), é realmente fácil:

"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --headless --screenshot --window-size=256,256 --default-background-color=0 button.html

Explicação do comando:

  • você executa o Chrome na linha de comando (mostrada aqui para o Mac, mas assumindo similar no Windows ou Linux)
  • --headless executa o Chrome sem abri-lo e sai após a conclusão do comando
  • --screenshotirá capturar uma captura de tela (observe que ele gera um arquivo chamado screenshot.pngna pasta em que o comando é executado)
  • --window-sizepermite capturar apenas uma parte da tela (o formato é --window-size=width,height)
  • --default-background-color=0é o truque de mágica que instrui o Chrome a usar um fundo transparente , não a cor branca padrão
  • finalmente, você fornece o arquivo html (como um URL local ou remoto ...)
yan
fonte
1
Muito agradável! Também funciona com SVG! Mudei para a sua solução no final! ;-)
Martin Delille
1
--screenshot='absolute\path\of\screenshot.png'trabalhou para mim
palash 24/01
linha de comman funcionou. se eu quiser executar isso programaticamente a partir do express js como executá-lo?
Squapl Recipes
Para sua informação, isso também funciona com cromo, embora não mencione a opção na página do manual.
Robin Lashof-Regas
O svg não é um svg para mim ... é apenas um PNG com uma extensão SVG ... então cuidado.
kristopolous
97

Sim. HTML2Canvas existe para renderizar HTML <canvas>(que você pode usar como imagem).

NOTA: Há um problema conhecido, que não funcionará com SVG

AKX
fonte
Parece interessante, mas não consegui fazê-lo funcionar, por isso escolhi a solução John Fisher. Obrigado pela informação, vou assistir a este projeto no futuro!
Martin Delille
@MartinDelille: aqui um artigo de usar HTML2Canvas para criar uma imagem e você também pode baixá-lo no cliente lado codepedia.info/2016/02/...
Satinder singh
3
dom-to-image (veja a resposta de tsayen) faz um trabalho muito melhor para renderizar uma imagem precisa.
JBE 6/16
3
Esta solução é muito lenta #
3051717
3
outra questão, se o elemento está escondido ou atrás de um outro elemento isto não vai funcionar
Yousef
91

Posso recomendar a biblioteca dom-to-image , que foi escrita apenas para solucionar esse problema (eu sou o mantenedor).
Aqui está como você o usa (um pouco mais aqui ):

var node = document.getElementById('my-node');

domtoimage.toPng(node)
    .then (function (dataUrl) {
        var img = new Image();
        img.src = dataUrl;
        document.appendChild(img);
    })
    .catch(function (error) {
        console.error('oops, something went wrong!', error);
    });
tsayen
fonte
2
Isso é ótimo! Alguma maneira de dobrar o tamanho da saída?
cronoklee
Obrigado! Mas o que exatamente você quer dizer com "dobrando o tamanho"?
tsayen
2
A primeira coisa que me vem à cabeça - tente renderizar sua imagem em SVG (usando domtoimage.toSvg), depois renderize-a na tela e tente brincar com a resolução dessa tela de alguma forma. Provavelmente é possível implementar esse recurso como algum tipo de opção de renderização na própria lib, para que você possa passar as dimensões da imagem em pixels. Se você precisar, agradecemos que você crie um problema no github.
tsayen
8
Isso é MUITO melhor que o html2canvas. Obrigado.
c.hughes
1
@Subho é uma cadeia contendo o URL com dados codificados em base 64
tsayen
66

Há muitas opções e todos têm seus prós e contras.

Opção 1: use uma das muitas bibliotecas disponíveis

Prós

  • A conversão é bastante rápida na maioria das vezes

Contras

  • Renderização incorreta
  • Não executa javascript
  • Não há suporte para recursos recentes da Web (FlexBox, Seletores avançados, Webfonts, Dimensionamento de caixas, Consultas de mídia, ...)
  • Às vezes não é tão fácil de instalar
  • Complicado à escala

Opção 2: use PhantomJs e talvez uma biblioteca de invólucros

Prós

  • Executar Javascript
  • Bem rápido

Contras

  • Renderização incorreta
  • Não há suporte para recursos recentes da Web (FlexBox, Seletores avançados, Webfonts, Dimensionamento de caixas, Consultas de mídia, ...)
  • Complicado à escala
  • Não é tão fácil fazê-lo funcionar se houver imagens a serem carregadas ...

Opção 3: use o Chrome Headless e talvez uma biblioteca de invólucros

Prós

  • Executar Javascript
  • Renderização quase perfeita

Contras

  • Não é tão fácil ter exatamente o resultado desejado em relação a:
    • tempo de carregamento da página
    • dimensões da janela de visualização
  • Complicado à escala
  • Muito lento e até mais lento se o html contiver links externos

Opção 4: use uma API

Prós

  • Executar Javascript
  • Renderização quase perfeita
  • Rápido quando as opções de cache são usadas corretamente
  • A escala é tratada pelas APIs
  • Tempo preciso, janela de visualização, ...
  • Na maioria das vezes, eles oferecem um plano gratuito

Contras

  • Não é gratuito se você planeja usá-los muito

Divulgação: Sou o fundador do ApiFlash. Eu fiz o meu melhor para fornecer uma resposta honesta e útil.

Timothée Jeannin
fonte
2
Obrigado por uma resposta detalhada com muitas soluções possíveis
bszom
O wkhtmltoimage / pdf suporta a renderização de javascript. Você pode definir um atraso javascript ou deixar de verificação wkhtml para uma window.status specifc (que você pode definir com javascript quando você sabe seu material js é feito)
Daniel Z.
O PhantomJS suporta coisas dinâmicas como o Google Maps. É realmente um navegador da Web completo, apenas sem uma tela anexada. No entanto, você precisa esperar um pouco para o Google Map carregar os blocos com a geografia (espero 500ms e permito que as pessoas alterem o atraso - se não for suficiente, rodar novamente sem aumentar o tempo é bom, porque esses blocos são armazenados em cache).
Ladislav Zima
50

Todas as respostas aqui usam bibliotecas de terceiros enquanto a renderização de HTML em uma imagem pode ser relativamente simples em Javascript puro. Não é era ainda um artigo sobre isso na seção de lona no MDN.

O truque é este:

  • crie um SVG com um nó ForeignObject contendo seu XHTML
  • defina o src de uma imagem para o URL de dados desse SVG
  • drawImage na tela
  • definir dados da tela para segmentar image.src

const {body} = document

const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
canvas.width = canvas.height = 100

const tempImg = document.createElement('img')
tempImg.addEventListener('load', onTempImageLoad)
tempImg.src = 'data:image/svg+xml,' + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100"><foreignObject width="100%" height="100%"><div xmlns="http://www.w3.org/1999/xhtml"><style>em{color:red;}</style><em>I</em> lick <span>cheese</span></div></foreignObject></svg>')

const targetImg = document.createElement('img')
body.appendChild(targetImg)

function onTempImageLoad(e){
  ctx.drawImage(e.target, 0, 0)
  targetImg.src = canvas.toDataURL()
}

Algumas coisas a serem observadas

  • O HTML dentro do SVG deve ser XHTML
  • Por motivos de segurança, o SVG como URL de dados de uma imagem atua como um escopo CSS isolado para o HTML, pois nenhuma fonte externa pode ser carregada. Portanto, uma fonte do Google, por exemplo, deve ser incorporada usando uma ferramenta como esta .
  • Mesmo quando o HTML dentro do SVG excede o tamanho da imagem, ele desenha na tela corretamente. Mas a altura real não pode ser medida a partir dessa imagem. Uma solução de altura fixa funcionará bem, mas a altura dinâmica exigirá um pouco mais de trabalho. O melhor é renderizar os dados SVG em um iframe (para escopo CSS isolado) e usar o tamanho resultante para a tela.
Sjeiti
fonte
Agradável! Remover a dependência da biblioteca externa é realmente o bom caminho a percorrer. Eu mudei minha resposta aceita :-)
Martin Delille
Esse artigo não está mais lá.
MSC
2
Ótima resposta, mas comentar sobre o estado da arte das APIs HTML e da Web, que, como de costume, parece algo puxado para trás - toda a funcionalidade está tecnicamente lá, mas exposta atrás de uma matriz (sem trocadilhos) de caminhos de código estranhos que se assemelham nada do tipo de clareza que você esperaria de uma API bem projetada. Em plainspeak: é provavelmente trivial para um navegador permitir que um Documentseja convertido em raster (por exemplo, a Canvas), mas como ninguém se incomodou em padronizá-lo, precisamos recorrer à insanidade, como ilustrado acima (sem ofensa ao autor desta código).
amn
1
Esta resposta stackoverflow.com/questions/12637395/… parece indicar que você pode ir muito longe. O Mozilla e o Chrome têm um número ilimitado de dados e até o IE atual permite 4 GB, que se resumem a imagens de cerca de 3 GB (devido à base64). Mas isso seria uma boa coisa para testar.
Sjeiti 1/01/19
3
- alguns testes rápidos e sujos mais tarde - jsfiddle.net/Sjeiti/pcwudrmc/75965 Chrome, Edge e Firefox atingem uma altura de largura de pelo menos 10.000 pixels, que (nesse caso) é uma contagem de caracteres de cerca de 10.000.000 e um tamanho de arquivo png de 8MB. Não foram testados rigorosamente, mas são suficientes para a maioria dos casos de uso.
Sjeiti
13

Você pode usar o PhantomJS , que é um driver do kit da web sem cabeça (o mecanismo de renderização no safari e (até recentemente) chrome). Você pode aprender como fazer a captura de tela de páginas aqui . Espero que ajude!

TheContrarian
fonte
Essa técnica funciona bem. No entanto, dois anos se passaram desde o seu comentário. Você encontrou algo que funcione mais rápido que o PhantomJS? A geração de imagens normalmente leva de 2 a 5 segundos no Phantom, na minha experiência.
Brian Webster
Chrome sem cabeça agora é possível. Não sei se é mais rápido, mas se você quiser capturas de tela precisas, este é um bom caminho a percorrer.
21419 Josh Maag
11

Você pode usar uma ferramenta HTML para PDF como wkhtmltopdf. E então você pode usar um PDF para ferramenta de imagem como imagemagick. É certo que este é o lado do servidor e um processo muito complicado ...

PinnyM
fonte
12
Ou você pode simplesmente executar o irmão da imagem de wkhtmltopdf, wkhtmltoimage.
Roman Starkov
1
Esta biblioteca é para Node.js. Que tal frontend simples?
Vlad
9

A única biblioteca que trabalhei no Chrome, Firefox e MS Edge foi rasterizeHTML . Ele produz uma melhor qualidade que o HTML2Canvas e ainda é suportado, diferentemente do HTML2Canvas.

Obtendo o elemento e fazendo o download como PNG

var node= document.getElementById("elementId");
var canvas = document.createElement("canvas");
canvas.height = node.offsetHeight;
canvas.width = node.offsetWidth;
var name = "test.png"

rasterizeHTML.drawHTML(node.outerHTML, canvas)
     .then(function (renderResult) {
            if (navigator.msSaveBlob) {
                window.navigator.msSaveBlob(canvas.msToBlob(), name);
            } else {
                const a = document.createElement("a");
                document.body.appendChild(a);
                a.style = "display: none";
                a.href = canvas.toDataURL();
                a.download = name;
                a.click();
                document.body.removeChild(a);
            }
     });
vabanagas
fonte
2
mas não funciona com o IE. Limitações de rasterizeHTML
Boban Stojanovski 15/02
@vabanagas existe algum arquivo javascript para isso?
Mahdi Khalili
Essa mudança me ajudou muito: rasterizeHTML.drawHTML(node.innerHTML, canvas) Nota que Im chamando innerHTML no nó
setembro GH
5

Use html2canvas: inclua o método de plug-in e chamada para converter HTML em Canvas e faça o download como imagem PNG

        html2canvas(document.getElementById("image-wrap")).then(function(canvas) {
            var link = document.createElement("a");
            document.body.appendChild(link);
            link.download = "manpower_efficiency.jpg";
            link.href = canvas.toDataURL();
            link.target = '_blank';
            link.click();
        });

Fonte : http://www.freakyjolly.com/convert-html-document-into-image-jpg-png-from-canvas/

Code Spy
fonte
4

Não espero que seja a melhor resposta, mas pareceu interessante o suficiente para postar.

Escreva um aplicativo que abra seu navegador favorito para o documento HTML desejado, dimensione a janela corretamente e faça uma captura de tela. Em seguida, remova as bordas da imagem.

John Fisher
fonte
4

Use este código, ele certamente funcionará:

<script type="text/javascript">
 $(document).ready(function () {
	 setTimeout(function(){
		 downloadImage();
	 },1000)
 });
 
 function downloadImage(){
	 html2canvas(document.querySelector("#dvContainer")).then(canvas => {
		a = document.createElement('a'); 
		document.body.appendChild(a); 
		a.download = "test.png"; 
		a.href =  canvas.toDataURL();
		a.click();
	});	 
 }
</script>

Só não se esqueça de incluir o arquivo Html2CanvasJS no seu programa. https://html2canvas.hertzen.com/dist/html2canvas.js

Mohit Kulkarni
fonte
2

Você não pode fazer isso 100% com precisão apenas com JavaScript.

Existe uma ferramenta Qt Webkit por aí e uma versão em python . Se você quiser fazer isso sozinho, tive sucesso com o cacau:

[self startTraverse:pagesArray performBlock:^(int collectionIndex, int pageIndex) {

    NSString *locale = [self selectedLocale];

    NSRect offscreenRect = NSMakeRect(0.0, 0.0, webView.frame.size.width, webView.frame.size.height);
    NSBitmapImageRep* offscreenRep = nil;      

    offscreenRep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:nil
                                             pixelsWide:offscreenRect.size.width
                                             pixelsHigh:offscreenRect.size.height
                                             bitsPerSample:8
                                             samplesPerPixel:4
                                             hasAlpha:YES
                                             isPlanar:NO
                                             colorSpaceName:NSCalibratedRGBColorSpace
                                             bitmapFormat:0
                                             bytesPerRow:(4 * offscreenRect.size.width)
                                             bitsPerPixel:32];

    [NSGraphicsContext saveGraphicsState];

    NSGraphicsContext *bitmapContext = [NSGraphicsContext graphicsContextWithBitmapImageRep:offscreenRep];
    [NSGraphicsContext setCurrentContext:bitmapContext];
    [webView displayRectIgnoringOpacity:offscreenRect inContext:bitmapContext];
    [NSGraphicsContext restoreGraphicsState];

    // Create a small + large thumbs
    NSImage *smallThumbImage = [[NSImage alloc] initWithSize:thumbSizeSmall];  
    NSImage *largeThumbImage = [[NSImage alloc] initWithSize:thumbSizeLarge];

    [smallThumbImage lockFocus];
    [[NSGraphicsContext currentContext] setImageInterpolation:NSImageInterpolationHigh];  
    [offscreenRep drawInRect:CGRectMake(0, 0, thumbSizeSmall.width, thumbSizeSmall.height)];  
    NSBitmapImageRep *smallThumbOutput = [[NSBitmapImageRep alloc] initWithFocusedViewRect:CGRectMake(0, 0, thumbSizeSmall.width, thumbSizeSmall.height)];  
    [smallThumbImage unlockFocus];  

    [largeThumbImage lockFocus];  
    [[NSGraphicsContext currentContext] setImageInterpolation:NSImageInterpolationHigh];  
    [offscreenRep drawInRect:CGRectMake(0, 0, thumbSizeLarge.width, thumbSizeLarge.height)];  
    NSBitmapImageRep *largeThumbOutput = [[NSBitmapImageRep alloc] initWithFocusedViewRect:CGRectMake(0, 0, thumbSizeLarge.width, thumbSizeLarge.height)];  
    [largeThumbImage unlockFocus];  

    // Write out small
    NSString *writePathSmall = [issueProvider.imageDestinationPath stringByAppendingPathComponent:[NSString stringWithFormat:@"/%@-collection-%03d-page-%03d_small.png", locale, collectionIndex, pageIndex]];
    NSData *dataSmall = [smallThumbOutput representationUsingType:NSPNGFileType properties: nil];
    [dataSmall writeToFile:writePathSmall atomically: NO];

    // Write out lage
    NSString *writePathLarge = [issueProvider.imageDestinationPath stringByAppendingPathComponent:[NSString stringWithFormat:@"/%@-collection-%03d-page-%03d_large.png", locale, collectionIndex, pageIndex]];
    NSData *dataLarge = [largeThumbOutput representationUsingType:NSPNGFileType properties: nil];
    [dataLarge writeToFile:writePathLarge atomically: NO];
}];

Espero que isto ajude!

Matt Melton
fonte
Você tem a versão rápida do acima? ou, se possível, você pode reescrevê-lo?
Ssh
Você se importaria de compartilhar o código usado para carregar o webView que você está processando? Parece uma abordagem promissora para o meu renderizador de miniaturas. Obrigado!
Dave
1

Instalar phantomjs

$ npm install phantomjs

Crie um arquivo github.js com o seguinte código

var page = require('webpage').create();
//viewportSize being the actual size of the headless browser
page.viewportSize = { width: 1024, height: 768 };
page.open('http://github.com/', function() {
    page.render('github.png');
    phantom.exit();
});

Passe o arquivo como argumento para phantomjs

$ phantomjs github.js
Saurabh Saxena
fonte
-3

Você pode adicionar a referência HtmlRenderer ao seu projeto e fazer o seguinte,

string htmlCode ="<p>This is a sample html.</p>";
Image image = HtmlRender.RenderToImage(htmlCode ,new Size(500,300));
kuttalam
fonte