Renderuj HTML do obrazu

167

Czy istnieje sposób na renderowanie HTML do obrazu, takiego jak PNG? Wiem, że jest to możliwe z płótnem, ale chciałbym renderować standardowy element html, na przykład div.

Martin Delille
źródło
Na przykład, aby utworzyć rodzaj pomocy dla użytkownika. Niestety nie jest to możliwe (ze względów bezpieczeństwa?). Musisz poprosić użytkownika o naciśnięcie PrintScreen, aby coś zrobić.
MaxArt
1
możliwy duplikat Jak przekonwertować HTML strony internetowej na obraz?
Ernest Friedman-Hill
Chcę używać HTML / CSS do projektowania logo.
Martin Delille
4
@ ErnestFriedman-Hill nie jest duplikatem: pytanie, o którym wspomniałeś, dotyczy języka java.
Martin Delille
Oto samouczek krok po kroku dotyczący konwersji HTML do formatu IMAGE png lub jpeg codepedia.info/2016/02/...
Satinder singh

Odpowiedzi:

42

Wiem, że to dość stare pytanie, które ma już wiele odpowiedzi, a mimo to godzinami próbowałem robić to, co chciałem:

  • mając plik html, wygeneruj obraz (png) z przezroczystym tłem z wiersza poleceń

Korzystając z Chrome headless (wersja 74.0.3729.157 z tej odpowiedzi), jest to w rzeczywistości łatwe:

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

Wyjaśnienie polecenia:

  • uruchamiasz Chrome z wiersza poleceń (tutaj pokazane dla Maca, ale zakładając podobne w Windows lub Linux)
  • --headless uruchamia Chrome bez otwierania go i zamyka po wykonaniu polecenia
  • --screenshotprzechwyci zrzut ekranu (pamiętaj, że generuje plik o nazwie screenshot.pngw folderze, w którym polecenie jest uruchamiane)
  • --window-sizepozwalają uchwycić tylko część ekranu (format jest --window-size=width,height)
  • --default-background-color=0to magiczna sztuczka, która mówi Chrome, że ma używać przezroczystego tła, a nie domyślnego białego koloru
  • na koniec podajesz plik html (jako adres URL lokalny lub zdalny ...)
yan
źródło
1
Bardzo dobrze! Działa również z SVG! W końcu przeszedłem na twoje rozwiązanie! ;-)
Martin Delille
1
--screenshot='absolute\path\of\screenshot.png'pracował dla mnie
palash
linia Comman zadziałała. jeśli chcę uruchomić to programowo z Express js, jak go uruchomić?
Squapl Recipes
FYI, działa to również z chromem, chociaż nie wspomina o opcji na stronie podręcznika.
Robin Lashof-Regas
Plik SVG nie jest dla mnie SVG ... to tylko PNG z rozszerzeniem SVG ... więc uważaj.
kristopolous
97

Tak. HTML2Canvas istnieje do renderowania kodu HTML <canvas>(którego można następnie użyć jako obrazu).

UWAGA: istnieje znany problem, że to nie zadziała z SVG

AKX
źródło
Wydaje się to interesujące, ale nie udało mi się tego zrobić, więc wybrałem rozwiązanie Johna Fishera. Dzięki za informację, będę oglądać ten projekt w przyszłości!
Martin Delille
@MartinDelille: tutaj artykuł o używaniu HTML2Canvas do tworzenia OBRAZÓW, który można również pobrać ze strony klienta codepedia.info/2016/02/…
Satinder singh
3
dom-to-image (patrz odpowiedź tsayen) znacznie lepiej radzi sobie z renderowaniem dokładnego obrazu.
JBE
3
To rozwiązanie jest bardzo powolne
hamboy75
3
kolejna kwestia, jeśli element jest ukryty lub za innym elementem, to nie zadziała
Yousef
91

Mogę polecić bibliotekę dom-to-image , która została napisana wyłącznie w celu rozwiązania tego problemu (jestem opiekunem).
Oto jak go używasz (więcej tutaj ):

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
źródło
2
To jest świetne! Jakiś sposób na podwojenie rozmiaru wyjściowego?
cronoklee
Dzięki! Ale co dokładnie masz na myśli mówiąc „podwojenie rozmiaru”?
tsayen
2
Pierwsza rzecz, która przychodzi mi do głowy - spróbuj wyrenderować obraz do SVG (używając domtoimage.toSvg), a następnie wyrenderuj go samodzielnie na płótnie i spróbuj jakoś pobawić się rozdzielczością tego płótna. Prawdopodobnie możliwe jest zaimplementowanie takiej funkcji, jak pewna opcja renderowania w samej bibliotece, dzięki czemu można przekazywać wymiary obrazu w pikselach. Jeśli tego potrzebujesz, byłbym wdzięczny za utworzenie numeru na github.
tsayen
8
To DUŻO lepsze niż html2canvas. Dzięki.
c.hughes,
1
@Subho to ciąg znaków zawierający adres URL z danymi zakodowanymi w base64
tsayen
66

Opcji jest wiele i wszystkie mają swoje wady i zalety.

Opcja 1: użyj jednej z wielu dostępnych bibliotek

Plusy

  • Konwersja przebiega przez większość czasu dość szybko

Cons

  • Złe renderowanie
  • Nie wykonuje javascript
  • Brak obsługi najnowszych funkcji internetowych (FlexBox, Advanced Selectors, Webfonts, Box Sizing, Media Queries, ...)
  • Czasami nie jest tak łatwo zainstalować
  • Skomplikowane w skali

Opcja 2: Użyj PhantomJs i być może biblioteki opakowującej

Plusy

  • Wykonaj JavaScript
  • Dosyć szybko

Cons

  • Złe renderowanie
  • Brak obsługi najnowszych funkcji internetowych (FlexBox, Advanced Selectors, Webfonts, Box Sizing, Media Queries, ...)
  • Skomplikowane w skali
  • Nie jest łatwo sprawić, by działało, jeśli są obrazy do załadowania ...

Opcja 3: użyj Chrome Headless i być może biblioteki otoki

Plusy

  • Wykonaj JavaScript
  • Prawie idealne renderowanie

Cons

  • Nie jest łatwo uzyskać dokładnie pożądany wynik dotyczący:
    • czas wczytywania strony
    • wymiary rzutni
  • Skomplikowane w skali
  • Dość wolno, a nawet wolniej, jeśli html zawiera linki zewnętrzne

Opcja 4: użyj interfejsu API

Plusy

  • Wykonaj JavaScript
  • Prawie idealne renderowanie
  • Szybko, gdy opcje buforowania są prawidłowo używane
  • Skalowanie jest obsługiwane przez interfejsy API
  • Dokładny czas, rzutnia, ...
  • Przez większość czasu oferują bezpłatny plan

Cons

  • Nie za darmo, jeśli planujesz często ich używać

Ujawnienie: jestem założycielem ApiFlash. Zrobiłem co w mojej mocy, aby udzielić uczciwej i użytecznej odpowiedzi.

Timothée Jeannin
źródło
2
Dziękuję za szczegółową odpowiedź z wieloma możliwymi rozwiązaniami
bszom
wkhtmltoimage / pdf obsługuje renderowanie javascript. Możesz ustawić opóźnienie javascript lub pozwolić wkhtml sprawdzić, czy istnieje konkretny window.status (który możesz ustawić za pomocą javascript, gdy wiesz, że wszystko jest gotowe)
Daniel
PhantomJS obsługuje dynamiczne rzeczy, takie jak Mapy Google. To naprawdę pełna przeglądarka internetowa, tylko bez podłączonego wyświetlacza. Jednak trzeba trochę poczekać, aż Google Map załaduje kafelki z geografią (czekam 500ms i pozwalam ludziom zmienić opóźnienie - jeśli to nie wystarczy, ponowne uruchomienie bez zwiększania czasu jest w porządku, ponieważ te kafelki są buforowane).
Ladislav Zima
50

Wszystkie odpowiedzi tutaj wykorzystują biblioteki stron trzecich, podczas gdy renderowanie HTML do obrazu może być stosunkowo proste w czystym JavaScript. Nie jest nawet artykuł o tym na odcinku płótno na MDN.

Sztuczka jest taka:

  • utwórz SVG z węzłem ForeignObject zawierającym Twój XHTML
  • ustaw źródło obrazu na adres URL danych tego pliku SVG
  • drawImage na płótnie
  • ustaw dane kanwy na docelowy 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()
}

Kilka rzeczy do zapamiętania

  • HTML wewnątrz SVG musi być XHTML
  • Ze względów bezpieczeństwa plik SVG jako adres URL obrazu działa jako izolowany zakres CSS dla HTML, ponieważ nie można załadować żadnych zewnętrznych źródeł. Na przykład czcionka Google musi być wbudowana za pomocą narzędzia takiego jak to .
  • Nawet jeśli kod HTML wewnątrz SVG przekracza rozmiar obrazu, zostanie poprawnie narysowany na płótnie. Ale rzeczywistej wysokości nie można zmierzyć na podstawie tego obrazu. Rozwiązanie o stałej wysokości będzie działać dobrze, ale dynamiczna wysokość będzie wymagać nieco więcej pracy. Najlepiej jest wyrenderować dane SVG w ramce iframe (dla izolowanego zakresu CSS) i użyć wynikowego rozmiaru dla płótna.
Sjeiti
źródło
Miły! Usunięcie zależności od zewnętrznych bibliotek jest rzeczywiście dobrym rozwiązaniem. Zmieniłem zaakceptowaną odpowiedź :-)
Martin Delille
Tego artykułu już nie ma.
MSC
2
Świetna odpowiedź, ale komentując stan techniki HTML i interfejsów API sieci Web, które jak zwykle wygląda jak coś wyciągniętego za kogoś - cała funkcjonalność jest tam technicznie, ale jest ujawniona za tablicą (gra słów nie zamierzona) dziwnych ścieżek kodu, które przypominają nic takiego, jakiego można by się spodziewać po dobrze zaprojektowanych interfejsach API. Mówiąc prościej: to jest najprawdopodobniej trywialne dla przeglądarki, aby pozwolić Documentna renderowanie a do rastra (np. A Canvas), ale ponieważ nikt nie zadał sobie trudu, aby go ujednolicić, musimy uciekać się do szaleństwa, jak pokazano powyżej (bez urazy dla autora tego kod).
amn
1
Ta odpowiedź stackoverflow.com/questions/12637395/… wydaje się wskazywać, że można zajść całkiem daleko. Mozilla i Chrome mają nieograniczoną długość danych, a nawet obecny IE pozwala na 4 GB, co sprowadza się do około 3 GB obrazów (ze względu na base64). Ale dobrze byłoby to przetestować.
Sjeiti
3
- kilka szybkich i brudnych testów później - jsfiddle.net/Sjeiti/pcwudrmc/75965 Chrome, Edge i Firefox osiągają szerokość co najmniej 10000 pikseli, co (w tym przypadku) jest liczbą znaków około 10000000 i rozmiar pliku png 8 MB. Nie był testowany rygorystycznie, ale wystarcza w większości przypadków użycia.
Sjeiti
13

Możesz użyć PhantomJS , który jest bezgłowym webkitem (silnik renderujący w Safari i (do niedawna) chrome). Można dowiedzieć się, jak zrobić zrzut ekranu stron tutaj . Mam nadzieję, że to pomoże!

TheContrarian
źródło
Ta technika działa dobrze. Jednak od twojego komentarza minęły 2 lata. Czy spotkałeś coś, co działa szybciej niż PhantomJS? Z mojego doświadczenia wynika, że ​​generowanie obrazu w Phantomie zajmuje zazwyczaj 2-5 sekund.
Brian Webster
Headless Chrome jest teraz możliwy. Nie wiem, czy jest szybsze, ale jeśli chcesz dokładnych zrzutów ekranu, to jest dobry sposób.
Josh Maag
11

Możesz użyć narzędzia HTML do PDF, takiego jak wkhtmltopdf. Następnie możesz użyć narzędzia PDF do obrazowania, takiego jak imagemagick. Trzeba przyznać, że jest to strona serwera i bardzo zawiły proces ...

PinnyM
źródło
12
Możesz też po prostu uruchomić obraz brat wkhtmltopdf, wkhtmltoimage.
Roman Starkov
1
Ta biblioteka jest przeznaczona dla Node.js. A co z prostym interfejsem użytkownika?
Vlad
9

Jedyną biblioteką, którą udało mi się pracować dla Chrome, Firefox i MS Edge, była rasterizeHTML . Zapewnia lepszą jakość niż HTML2Canvas i nadal jest obsługiwany w przeciwieństwie do HTML2Canvas.

Pobieranie elementu i pobieranie jako 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
źródło
2
ale nie działa z IE. rasterizeHTML Ograniczenia
Boban Stojanovski
@vabanagas czy jest do tego jeden plik javascript?
Mahdi Khalili
Ta zmiana bardzo mi pomogła: rasterizeHTML.drawHTML(node.innerHTML, canvas) Zwróć uwagę, że dzwonię do innerHTML na węźle
wrzesień GH
5

Użyj html2canvas, po prostu dołącz wtyczkę i wywołaj metodę, aby przekonwertować HTML na Canvas, a następnie pobrać jako obraz 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();
        });

Źródło : http://www.freakyjolly.com/convert-html-document-into-image-jpg-png-from-canvas/

Code Spy
źródło
4

Nie spodziewam się, że to będzie najlepsza odpowiedź, ale opublikowanie tego wydawało się wystarczająco interesujące.

Napisz aplikację, która otwiera Twoją ulubioną przeglądarkę na żądany dokument HTML, odpowiednio dopasowuje rozmiar okna i wykonuje zrzut ekranu. Następnie usuń krawędzie obrazu.

John Fisher
źródło
4

Użyj tego kodu, na pewno zadziała:

<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>

Nie zapomnij tylko o dołączeniu pliku Html2CanvasJS do swojego programu. https://html2canvas.hertzen.com/dist/html2canvas.js

Mohit Kulkarni
źródło
2

Nie możesz tego zrobić w 100% dokładnie za pomocą samego JavaScript.

Jest tam narzędzie Qt Webkit i wersja dla Pythona . Jeśli chcesz to zrobić sam, odniosłem sukces z Cocoa:

[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];
}];

Mam nadzieję że to pomoże!

Matt Melton
źródło
Czy masz szybką wersję powyższego? lub jeśli to możliwe, czy możesz napisać go ponownie?
ssh
Czy mógłbyś udostępnić kod, którego używasz do ładowania webView, który renderujesz? Wydaje się, że to obiecujące podejście do mojego modułu renderowania miniatur. Dzięki!
Dave,
1

Zainstaluj phantomjs

$ npm install phantomjs

Utwórz plik github.js z następującym kodem

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();
});

Przekaż plik jako argument do phantomjs

$ phantomjs github.js
Saurabh Saxena
źródło
-3

Możesz dodać referencyjny HtmlRenderer do swojego projektu i wykonać następujące czynności,

string htmlCode ="<p>This is a sample html.</p>";
Image image = HtmlRender.RenderToImage(htmlCode ,new Size(500,300));
kuttalam
źródło