1678272900
The package implements dropdown navbar, it based on Tailwind CSS.
$ npm install vue3-dropdown-navbar
<script setup lang="ts">
import {
TheDropDownNavbar,
TheDropDownMenu,
TheDropDownItem,
TheDropDownDivideBlock,
TheDropDownNavbarLogo,
} from "vue3-dropdown-navbar";
import { ref } from "vue";
const dropdownMenu = ref<InstanceType<typeof TheDropDownMenu>>();
const toggleDarkMode = () => {
document.documentElement.classList.toggle("dd-nav-dark");
};
</script>
<template>
<TheDropDownNavbar>
<template #logo>
<TheDropDownNavbarLogo>Vue3 DropDown Navbar</TheDropDownNavbarLogo>
</template>
<TheDropDownItem link="/home">Home</TheDropDownItem>
<TheDropDownMenu text="Dropdown 1" ref="dropdownMenu">
<TheDropDownItem>Dropdown Item 1</TheDropDownItem>
<TheDropDownItem>Dropdown Item 2</TheDropDownItem>
<TheDropDownMenu text="Dropdown Item 3">
<TheDropDownItem>Dropdown Item 3-1</TheDropDownItem>
<TheDropDownItem>Dropdown Item 3-2</TheDropDownItem>
</TheDropDownMenu>
<TheDropDownDivideBlock>
<TheDropDownItem>Sign out</TheDropDownItem>
</TheDropDownDivideBlock>
</TheDropDownMenu>
<TheDropDownItem @click="toggleDarkMode">Toggle Dark Mode</TheDropDownItem>
</TheDropDownNavbar>
<button
class="border bg-blue-500 text-white px-4 py-2 rounded-lg"
@click="dropdownMenu?.openDropdownMenu"
>
Open Dropdown Menu
</button>
</template>
Assume you have not installed TailwindCSS, you may reset the style, can import preflight in main.(js|ts)
, it provides by Tailwind CSS.
import { createApp } from "vue";
import App from "./App.vue";
// ...
import "vue3-dropdown-navbar/preflight.css"; // <-- Add this
const app = createApp(App);
app.use(router);
app.mount("#app");
if the suffix is (?), which is an optional property.
TheDropDownNavbarLogo
component available properties: Type | Name | Description | ---------------|-----|---| String | link? | set link. | String | imageUrl? | set image url. | String | alt? | set image alt property. | Boolean | native? | set whether use native (<a>) link. |
TheDropDownMenu
component available properties: Type | Name | Description | ---------------|-----|---| String | text | set text for dropdown menu. | Boolean | closeOthers? | set whether close other menus when open this menu. |
TheDropDownItem
component available properties: Type | Name | Description | ---------------|-----|---| String | link? | set link. | Boolean | native? | set whether use native (<a>) link. |
TheDropDownNavbar
component available function: Name | Description | ----------------|----------| closeAllDropdownMenu | close all dropdown menus. |
TheDropDownMenu
component available function: Name | Description | ----------------|----------| openDropdownMenu | open this dropdown menu. | closeDropdownMenu | close this dropdown menu. |
TheDropDownNavbar
component available slot: Name | Description | ----------------|----------| logo | set navbar left side content, recommand using TheDropDownNavbarLogo
component. |
Name | Description |
---|---|
no-close-others | by default, dropdown menu closes when clicking another element, if you want to click a specific element without closing dropdown menu, this can help you. |
Example
<button no-close-others>Click Me Without Close Dropdown Menu</button>
Add dd-nav-dark
class to <html> tag.
Author: LaiJunBin
Source code: https://github.com/LaiJunBin/vue3-dropdown-navbar
1667697000
The CNCF Cloud Native Landscape Project is intended as a map through the previously uncharted terrain of cloud native technologies. This attempts to categorize most of the projects and product offerings in the cloud native space. There are many routes to deploying a cloud native application, with CNCF Projects representing a particularly well-traveled path. It has been built in collaboration with Redpoint Ventures and Amplify Partners.
The software for the interactive landscape has been extracted to https://github.com/cncf/landscapeapp where it is used for other landscapes as well. This repo includes all of the data and images specific to the CNCF landscapes.
Please see landscape.cncf.io.
If you have node.js installed locally, just run ./server.js
from the root directory of the project and open http://localhost:8001 once the site is updated.
It requires any node.js version 14+ to be installed, does not use any npm package. It is both a server and a site builder - after any change the new site is built.
If you prefer to use Docker, run make serve-docker
. This will build and run a container serving the landscape at http://localhost:8001.
If you think your company or project should be included, please open a pull request to add it in alphabetical order to landscape.yml. For the logo, add an SVG to the hosted_logos
directory and reference it there.
Netlify will generate a staging server for you to preview your updates. Please check that the logo and information appear correctly and then add LGTM
to the pull request confirming your review and requesting a merge.
The following rules will produce the most readable and attractive logos:
hosted_logos
directory.Directions for fixing.
Please open a pull request with edits to landscape.yml. The file processed_landscape.yml is generated and so should never be edited directly.
If the error is with data from Crunchbase you should open an account there and edit the data. If you don't like a project description, edit it in GitHub. If your project isn't showing the license correctly, you may need to paste the unmodified text of the license into a LICENSE file at the root of your project in GitHub, in order for GitHub to serve the license information correctly.
The canonical source for all data is landscape.yml. Once a day, we download data for projects and companies from the following sources:
The update server enhances the source data with the fetched data and saves the result in processed_landscape.yml and as a JSON file, the latter of which is what the app loads to display data.
As explained at https://bestpractices.coreinfrastructure.org/:
The Linux Foundation (LF) Core Infrastructure Initiative (CII) Best Practices badge is a way for Free/Libre and Open Source Software (FLOSS) projects to show that they follow best practices. Projects can voluntarily self-certify, at no cost, by using this web application to explain how they follow each best practice. The CII Best Practices Badge is inspired by the many badges available to projects on GitHub. Consumers of the badge can quickly assess which FLOSS projects are following best practices and as a result are more likely to produce higher-quality secure software.
The interactive landscape displays the status (or non-existence) of a badge for each open-source project. There's also a feature not available through the filter bar to see all items with and without badges. Note that a passing badge is a requirement for projects to graduate in the CNCF.
For projects using a GitHub organisation, and the url used for the badge is the organisation not the repository, url_for_bestpractices
needs to be specified in the projects section in the landscape.yml.
We generally remove open source projects that have not had a commit in over 3 months. Note that for projects not hosted on GitHub, we need them to mirror to GitHub to fetch updates, and we try to work with projects when their mirrors are broken. Here is view of projects sorted by last update (ignoring categories like KCSPs, Certified Kubernetes, and members): https://landscape.cncf.io/card-mode?category=provisioning,runtime,orchestration-management,app-definition-and-development,paa-s-container-service,serverless,observability-and-analysis,runtime,installable-platform&license=open-source&grouping=no&sort=latest-commit
We generally remove closed source products when they have not tweeted in over 3 months. This doesn't apply to Chinese companies without Twitter accounts, since Twitter is blocked there. Here is a view of products sorted by last tweet (ignoring categories like KCSPs, Certified Kubernetes, and members): https://landscape.cncf.io/card-mode?category=provisioning,runtime,orchestration-management,app-definition-and-development,paa-s-container-service,serverless,observability-and-analysis&license=not-open-source&grouping=no&sort=latest-tweet
Items that have been removed can apply to be re-added using the regular New Entries criteria above.
The CNCF Cloud Native Landscape is available in these formats:
The CNCF Serverless Landscape is available in these formats:
You can install and run locally with the install directions. It's not necessary to install locally if you just want to edit landscape.yml. You can do so via the GitHub web interface.
Please open an issue or, for sensitive information, email info@cncf.io.
The file src/components/MainContent2.js describes the key elements of a landscape big picture. It specifies where to put these sections: App Definition and Development, Orchestration & Management, Runtime, Provisioning, Cloud, Platform, Observability and Analysis, Special. Also it specifies where to locate the link to the serverless preview and an info with a QR code.
All these elements should have top
, left
, width
and height
properties to position them. rows
and cols
specify how much columns or rows we expect in a given horizontal or vertical section.
When we see that those elements can not fit the sections, we need to either increase the width of all the horizontal sections, or increase height and amount of rows in a single horizontal section and adjust the position of sections below.
Beside that, we have to adjust the width of a parent div (1620), the width in a src/components/BigPicture/FullscreenLandscape.js
(1640) and the width in a tools/renderLandscape.js
(6560, because of x4 zoom and margins)
Serverless has a same approach, files are src/components/BigPicture/ServerlessContent.js
, src/components/BigPicture/FullscreenServerless.js
and tools/renderLandscape.js
, with a full width of 3450 (because of x3 zoom and margins)
Sometimes the total height is changed too, then we need to adjust the height the same way as we adjust the width.
We have an experimental fitWidth
property, it is good when you want to get rid of an extra space on the right of a section.
The best way to test that layout is ok, is to visit /landscape
and /serverless
, and if it looks ok, run PORT=3000 babel-node tools/renderLandscape
and see the rendered png files, they are in src/images
folder
Author: cncf
Source Code: https://github.com/cncf/landscape
License: Apache-2.0 license
1662479160
SwiftSoup
is a pure Swift library, cross-platform (macOS, iOS, tvOS, watchOS and Linux!), for working with real-world HTML. It provides a very convenient API for extracting and manipulating data, using the best of DOM, CSS, and jQuery-like methods. SwiftSoup
implements the WHATWG HTML5 specification, and parses HTML to the same DOM as modern browsers do.
SwiftSoup
is designed to deal with all varieties of HTML found in the wild; from pristine and validating, to invalid tag-soup; SwiftSoup
will create a sensible parse tree.>=2.0.0
Swift 4.2 1.7.4
SwiftSoup is available through CocoaPods. To install it, simply add the following line to your Podfile:
pod 'SwiftSoup'
SwiftSoup is also available through Carthage. To install it, simply add the following line to your Cartfile:
github "scinfu/SwiftSoup"
SwiftSoup is also available through Swift Package Manager. To install it, simply add the dependency to your Package.Swift file:
...
dependencies: [
.package(url: "https://github.com/scinfu/SwiftSoup.git", from: "2.4.3"),
],
targets: [
.target( name: "YourTarget", dependencies: ["SwiftSoup"]),
]
...
pod try SwiftSoup
To parse an HTML document:
do {
let html = "<html><head><title>First parse</title></head>"
+ "<body><p>Parsed HTML into a doc.</p></body></html>"
let doc: Document = try SwiftSoup.parse(html)
return try doc.text()
} catch Exception.Error(let type, let message) {
print(message)
} catch {
print("error")
}
<p>Lorem <p>Ipsum
parses to <p>Lorem</p> <p>Ipsum</p>
)<td>Table data</td>
is wrapped into a <table><tr><td>...
)html
containing a head
and body
, and only appropriate elements within the head)Document
extends Element
extends Node.TextNode
extends Node
.Extract attributes, text, and HTML from elements
After parsing a document, and finding some elements, you'll want to get at the data inside those elements.
Node.attr(_ String key)
methodElement.text()
Element.html()
, or Node.outerHtml()
as appropriatedo {
let html: String = "<p>An <a href='http://example.com/'><b>example</b></a> link.</p>"
let doc: Document = try SwiftSoup.parse(html)
let link: Element = try doc.select("a").first()!
let text: String = try doc.body()!.text() // "An example link."
let linkHref: String = try link.attr("href") // "http://example.com/"
let linkText: String = try link.text() // "example"
let linkOuterH: String = try link.outerHtml() // "<a href="http://example.com/"><b>example</b></a>"
let linkInnerH: String = try link.html() // "<b>example</b>"
} catch Exception.Error(let type, let message) {
print(message)
} catch {
print("error")
}
The methods above are the core of the element data access methods. There are additional others:
Element.id()
Element.tagName()
Element.className()
and Element.hasClass(_ String className)
All of these accessor methods have corresponding setter methods to change the data.
Parse a document from a String
You have HTML in a Swift String, and you want to parse that HTML to get at its contents, or to make sure it's well formed, or to modify it. The String may have come from user input, a file, or from the web.
Use the static SwiftSoup.parse(_ html: String)
method, or SwiftSoup.parse(_ html: String, _ baseUri: String)
.
do {
let html = "<html><head><title>First parse</title></head>"
+ "<body><p>Parsed HTML into a doc.</p></body></html>"
let doc: Document = try SwiftSoup.parse(html)
return try doc.text()
} catch Exception.Error(let type, let message) {
print("")
} catch {
print("")
}
The parse(_ html: String, _ baseUri: String)
method parses the input HTML into a new Document
. The base URI argument is used to resolve relative URLs into absolute URLs, and should be set to the URL where the document was fetched from. If that's not applicable, or if you know the HTML has a base element, you can use the parse(_ html: String)
method.
As long as you pass in a non-null string, you're guaranteed to have a successful, sensible parse, with a Document containing (at least) a head
and a body
element.
Once you have a Document
, you can get at the data using the appropriate methods in Document
and its supers Element
and Node
.
Parsing a body fragment
You have a fragment of body HTML (e.g. div
containing a couple of p tags; as opposed to a full HTML document) that you want to parse. Perhaps it was provided by a user submitting a comment, or editing the body of a page in a CMS.
Use the SwiftSoup.parseBodyFragment(_ html: String)
method.
do {
let html: String = "<div><p>Lorem ipsum.</p>"
let doc: Document = try SwiftSoup.parseBodyFragment(html)
let body: Element? = doc.body()
} catch Exception.Error(let type, let message) {
print(message)
} catch {
print("error")
}
The parseBodyFragment
method creates an empty shell document, and inserts the parsed HTML into the body
element. If you used the normal SwiftSoup(_ html: String)
method, you would generally get the same result, but explicitly treating the input as a body fragment ensures that any bozo HTML provided by the user is parsed into the body
element.
The Document.body()
method retrieves the element children of the document's body
element; it is equivalent to doc.getElementsByTag("body")
.
If you are going to accept HTML input from a user, you need to be careful to avoid cross-site scripting attacks. See the documentation for the Whitelist
based cleaner, and clean the input with clean(String bodyHtml, Whitelist whitelist)
.
Sanitize untrusted HTML (to prevent XSS)
You want to allow untrusted users to supply HTML for output on your website (e.g. as comment submission). You need to clean this HTML to avoid cross-site scripting (XSS) attacks.
Use the SwiftSoup HTML Cleaner
with a configuration specified by a Whitelist
.
do {
let unsafe: String = "<p><a href='http://example.com/' onclick='stealCookies()'>Link</a></p>"
let safe: String = try SwiftSoup.clean(unsafe, Whitelist.basic())!
// now: <p><a href="http://example.com/" rel="nofollow">Link</a></p>
} catch Exception.Error(let type, let message) {
print(message)
} catch {
print("error")
}
A cross-site scripting attack against your site can really ruin your day, not to mention your users'. Many sites avoid XSS attacks by not allowing HTML in user submitted content: they enforce plain text only, or use an alternative markup syntax like wiki-text or Markdown. These are seldom optimal solutions for the user, as they lower expressiveness, and force the user to learn a new syntax.
A better solution may be to use a rich text WYSIWYG editor (like CKEditor or TinyMCE). These output HTML, and allow the user to work visually. However, their validation is done on the client side: you need to apply a server-side validation to clean up the input and ensure the HTML is safe to place on your site. Otherwise, an attacker can avoid the client-side Javascript validation and inject unsafe HMTL directly into your site
The SwiftSoup whitelist sanitizer works by parsing the input HTML (in a safe, sand-boxed environment), and then iterating through the parse tree and only allowing known-safe tags and attributes (and values) through into the cleaned output.
It does not use regular expressions, which are inappropriate for this task.
SwiftSoup provides a range of Whitelist
configurations to suit most requirements; they can be modified if necessary, but take care.
The cleaner is useful not only for avoiding XSS, but also in limiting the range of elements the user can provide: you may be OK with textual a
, strong
elements, but not structural div
or table
elements.
Cleaner
reference if you want to get a Document
instead of a String returnWhitelist
reference for the different canned options, and to create a custom whitelistSet attribute values
You have a parsed document that you would like to update attribute values on, before saving it out to disk, or sending it on as a HTTP response.
Use the attribute setter methods Element.attr(_ key: String, _ value: String)
, and Elements.attr(_ key: String, _ value: String)
.
If you need to modify the class attribute of an element, use the Element.addClass(_ className: String)
and Element.removeClass(_ className: String)
methods.
The Elements
collection has bulk attribute and class methods. For example, to add a rel="nofollow"
attribute to every a
element inside a div:
do {
try doc.select("div.comments a").attr("rel", "nofollow")
} catch Exception.Error(let type, let message) {
print(message)
} catch {
print("error")
}
Like the other methods in Element
, the attr methods return the current Element
(or Elements
when working on a collection from a select). This allows convenient method chaining:
do {
try doc.select("div.masthead").attr("title", "swiftsoup").addClass("round-box")
} catch Exception.Error(let type, let message) {
print(message)
} catch {
print("error")
}
Set the HTML of an element
You need to modify the HTML of an element.
Use the HTML setter methods in Element
:
do {
let doc: Document = try SwiftSoup.parse("<div>One</div><span>One</span>")
let div: Element = try doc.select("div").first()! // <div>One</div>
try div.html("<p>lorem ipsum</p>") // <div><p>lorem ipsum</p></div>
try div.prepend("<p>First</p>")
try div.append("<p>Last</p>")
print(div)
// now div is: <div><p>First</p><p>lorem ipsum</p><p>Last</p></div>
let span: Element = try doc.select("span").first()! // <span>One</span>
try span.wrap("<li><a href='http://example.com/'></a></li>")
print(doc)
// now: <html><head></head><body><div><p>First</p><p>lorem ipsum</p><p>Last</p></div><li><a href="http://example.com/"><span>One</span></a></li></body></html>
} catch Exception.Error(let type, let message) {
print(message)
} catch {
print("error")
}
Element.html(_ html: String)
clears any existing inner HTML in an element, and replaces it with parsed HTML.Element.prepend(_ first: String)
and Element.append(_ last: String)
add HTML to the start or end of an element's inner HTML, respectivelyElement.wrap(_ around: String)
wraps HTML around the outer HTML of an element.You can also use the Element.prependElement(_ tag: String)
and Element.appendElement(_ tag: String)
methods to create new elements and insert them into the document flow as a child element.
Setting the text content of elements
You need to modify the text content of an HTML document.
Solution
Use the text setter methods of Element
:
do {
let doc: Document = try SwiftSoup.parse("<div></div>")
let div: Element = try doc.select("div").first()! // <div></div>
try div.text("five > four") // <div>five > four</div>
try div.prepend("First ")
try div.append(" Last")
// now: <div>First five > four Last</div>
} catch Exception.Error(let type, let message) {
print(message)
} catch {
print("error")
}
The text setter methods mirror the [[HTML setter|Set the HTML of an element]] methods:
Element.text(_ text: String)
clears any existing inner HTML in an element, and replaces it with the supplied text.Element.prepend(_ first: String)
and Element.append(_ last: String)
add text nodes to the start or end of an element's inner HTML, respectively The text should be supplied unencoded: characters like <
, >
etc will be treated as literals, not HTML.Use DOM methods to navigate a document
You have a HTML document that you want to extract data from. You know generally the structure of the HTML document.
Use the DOM-like methods available after parsing HTML into a Document
.
do {
let html: String = "<a id=1 href='?foo=bar&mid<=true'>One</a> <a id=2 href='?foo=bar<qux&lg=1'>Two</a>"
let els: Elements = try SwiftSoup.parse(html).select("a")
for link: Element in els.array() {
let linkHref: String = try link.attr("href")
let linkText: String = try link.text()
}
} catch Exception.Error(let type, let message) {
print(message)
} catch {
print("error")
}
Elements provide a range of DOM-like methods to find elements, and extract and manipulate their data. The DOM getters are contextual: called on a parent Document they find matching elements under the document; called on a child element they find elements under that child. In this way you can window in on the data you want.
getElementById(_ id: String)
getElementsByTag(_ tag:String)
getElementsByClass(_ className: String)
getElementsByAttribute(_ key: String)
(and related methods)siblingElements()
, firstElementSibling()
, lastElementSibling()
, nextElementSibling()
, previousElementSibling()
parent()
, children()
, child(_ index: Int)
Element data
attr(_ key: Strin)
to get and attr(_ key: String, _ value: String)
to set attributesattributes()
to get all attributesid()
, className()
and classNames()
text()
to get and text(_ value: String)
to set the text contenthtml()
to get and html(_ value: String)
to set the inner HTML contentouterHtml()
to get the outer HTML valuedata()
to get data content (e.g. of script and style tags)tag()
and tagName()
append(_ html: String)
, prepend(html: String)
appendText(text: String)
, prependText(text: String)
appendElement(tagName: String)
, prependElement(tagName: String)
html(_ value: String)
Use selector syntax to find elements
You want to find or manipulate elements using a CSS or jQuery-like selector syntax.
Use the Element.select(_ selector: String)
and Elements.select(_ selector: String)
methods:
do {
let doc: Document = try SwiftSoup.parse("...")
let links: Elements = try doc.select("a[href]") // a with href
let pngs: Elements = try doc.select("img[src$=.png]")
// img with src ending .png
let masthead: Element? = try doc.select("div.masthead").first()
// div with class=masthead
let resultLinks: Elements? = try doc.select("h3.r > a") // direct a after h3
} catch Exception.Error(let type, let message) {
print(message)
} catch {
print("error")
}
SwiftSoup elements support a CSS (or jQuery) like selector syntax to find matching elements, that allows very powerful and robust queries.
The select
method is available in a Document
, Element
, or in Elements
. It is contextual, so you can filter by selecting from a specific element, or by chaining select calls.
Select returns a list of Elements
(as Elements
), which provides a range of methods to extract and manipulate the results.
tagname
: find elements by tag, e.g. a
ns|tag
: find elements by tag in a namespace, e.g. fb|name
finds <fb:name>
elements#id
: find elements by ID, e.g. #logo
.class
: find elements by class name, e.g. .masthead
[attribute]
: elements with attribute, e.g. [href]
[^attr]
: elements with an attribute name prefix, e.g. [^data-]
finds elements with HTML5 dataset attributes[attr=value]
: elements with attribute value, e.g. [width=500]
(also quotable, like [data-name='launch sequence']
)[attr^=value]
, [attr$=value]
, [attr*=value]
: elements with attributes that start with, end with, or contain the value, e.g. [href*=/path/]
[attr~=regex]
: elements with attribute values that match the regular expression; e.g. img[src~=(?i)\.(png|jpe?g)]
*
: all elements, e.g. *
el#id
: elements with ID, e.g. div#logo
el.class
: elements with class, e.g. div.masthead
el[attr]
: elements with attribute, e.g. a[href]
a[href].highlight
child
: child elements that descend from ancestor, e.g. .body p
finds p
elements anywhere under a block with class "body"parent > child
: child elements that descend directly from parent, e.g. div.content > p
finds p elements; and body > *
finds the direct children of the body tagsiblingA + siblingB
: finds sibling B element immediately preceded by sibling A, e.g. div.head + div
siblingA ~ siblingX
: finds sibling X element preceded by sibling A, e.g. h1 ~ p
el
, el
, el
: group multiple selectors, find unique elements that match any of the selectors; e.g. div.masthead
, div.logo
:lt(n)
: find elements whose sibling index (i.e. its position in the DOM tree relative to its parent) is less than n; e.g. td:lt(3)
:gt(n)
: find elements whose sibling index is greater than n; e.g. div p:gt(2)
:eq(n)
: find elements whose sibling index is equal to n; e.g. form input:eq(1)
:has(seletor)
: find elements that contain elements matching the selector; e.g. div:has(p)
:not(selector)
: find elements that do not match the selector; e.g. div:not(.logo)
:contains(text)
: find elements that contain the given text. The search is case-insensitive; e.g. p:contains(swiftsoup)
:containsOwn(text)
: find elements that directly contain the given text:matches(regex)
: find elements whose text matches the specified regular expression; e.g. div:matches((?i)login)
:matchesOwn(regex)
: find elements whose own text matches the specified regular expressionExamples
let html = "<html><head><title>First parse</title></head><body><p>Parsed HTML into a doc.</p></body></html>"
guard let doc: Document = try? SwiftSoup.parse(html) else { return }
guard let elements = try? doc.getAllElements() else { return html }
for element in elements {
for textNode in element.textNodes() {
[...]
}
}
try doc.head()?.append("<style>html {font-size: 2em}</style>")
let html = "<div class=\"container-fluid\">"
+ "<div class=\"panel panel-default \">"
+ "<div class=\"panel-body\">"
+ "<form id=\"coupon_checkout\" action=\"http://uat.all.com.my/checkout/couponcode\" method=\"post\">"
+ "<input type=\"hidden\" name=\"transaction_id\" value=\"4245\">"
+ "<input type=\"hidden\" name=\"lang\" value=\"EN\">"
+ "<input type=\"hidden\" name=\"devicetype\" value=\"\">"
+ "<div class=\"input-group\">"
+ "<input type=\"text\" class=\"form-control\" id=\"coupon_code\" name=\"coupon\" placeholder=\"Coupon Code\">"
+ "<span class=\"input-group-btn\">"
+ "<button class=\"btn btn-primary\" type=\"submit\">Enter Code</button>"
+ "</span>"
+ "</div>"
+ "</form>"
+ "</div>"
+ "</div>"
guard let doc: Document = try? SwiftSoup.parse(html) else { return } // parse html
let elements = try doc.select("[name=transaction_id]") // query
let transaction_id = try elements.get(0) // select first element
let value = try transaction_id.val() // get value
print(value) // 4245
guard let doc: Document = try? SwiftSoup.parse(html) else { return } // parse html
guard let txt = try? doc.text() else { return }
print(txt)
let xml = "<?xml version='1' encoding='UTF-8' something='else'?><val>One</val>"
guard let doc = try? SwiftSoup.parse(xml, "", Parser.xmlParser()) else { return }
guard let element = try? doc.getElementsByTag("val").first() else { return } // Find first element
try element.text("NewValue") // Edit Value
let valueString = try element.text() // "NewValue"
<img src>
do {
let doc: Document = try SwiftSoup.parse(html)
let srcs: Elements = try doc.select("img[src]")
let srcsStringArray: [String?] = srcs.array().map { try? $0.attr("src").description }
// do something with srcsStringArray
} catch Exception.Error(_, let message) {
print(message)
} catch {
print("error")
}
href
of <a>
let html = "<a id=1 href='?foo=bar&mid<=true'>One</a> <a id=2 href='?foo=bar<qux&lg=1'>Two</a>"
guard let els: Elements = try? SwiftSoup.parse(html).select("a") else { return }
for element: Element in els.array() {
print(try? element.attr("href"))
}
Output:
"?foo=bar&mid<=true"
"?foo=bar<qux&lg=1"
let text = "Hello &<> Å å π 新 there ¾ © »"
print(Entities.escape(text))
print(Entities.unescape(text))
print(Entities.escape(text, OutputSettings().encoder(String.Encoding.ascii).escapeMode(Entities.EscapeMode.base)))
print(Entities.escape(text, OutputSettings().charset(String.Encoding.ascii).escapeMode(Entities.EscapeMode.extended)))
print(Entities.escape(text, OutputSettings().charset(String.Encoding.ascii).escapeMode(Entities.EscapeMode.xhtml)))
print(Entities.escape(text, OutputSettings().charset(String.Encoding.utf8).escapeMode(Entities.EscapeMode.extended)))
print(Entities.escape(text, OutputSettings().charset(String.Encoding.utf8).escapeMode(Entities.EscapeMode.xhtml)))
Output:
"Hello &<> Å å π 新 there ¾ © »"
"Hello &<> Å å π 新 there ¾ © »"
"Hello &<> Å å π 新 there ¾ © »"
"Hello &<> Å å π 新 there ¾ © »"
"Hello &<> Å å π 新 there ¾ © »"
"Hello &<> Å å π 新 there ¾ © »"
"Hello &<> Å å π 新 there ¾ © »"
Nabil Chatbi, scinfu@gmail.com
SwiftSoup was ported to Swift from Java Jsoup library.
SwiftSoup is available under the MIT license. See the LICENSE file for more info.
Author: scinfu
Source code: https://github.com/scinfu/SwiftSoup
License: MIT license
#swift
1659112380
最近、プロジェクトに「PDFとしてダウンロード」機能という新しい機能を提供する必要がありました。私が最初に疑問に思ったのは、なぜこの機能を提供する必要があるのかということでした。マウスの右クリック/印刷/「PDFとして保存」オプションを備えたほとんどすべてのWebブラウザにネイティブに存在していませんか?さて、私は自分のWebページを試してみましたが、結果は本当に残念でした。
Boursoramaランダムページの例。次のようになります。
レンダリング:
その時、「この機能は価値があるかもしれない」と言ったのですが、どうすればいいですか?PDFを生成できる多くのオープンソースライブラリがあります。しかし、私の選択は、Googleが開発した有名なライブラリであるPuppeteerに自然に行き着きました。私によると、これはシングルページアプリケーションのPDF生成を処理する最も簡単な方法です。javascriptバンドルを処理せず、プレーンHTML / CSSを処理する場合は、そうではない可能性があります。実際、このユースケースには、たとえばwkhtmltopdf、html-pdf-node、jspdfなどのより簡単なソリューションがあります。
この記事では、Puppeteerを使用してSPAの美しいPDFを生成するためのヒントをいくつか紹介します。まず、ReactとPuppeteerを使用してページの印刷可能なバージョンを作成する方法を説明します。次に、Puppeteerを使用して新しい印刷可能なページを生成する方法を説明します。
この部分では、実際にPuppeteerやその他のプリンタサービスを設定する必要はありません。通常どおりコードに変更を加えてから、ページでCtrl + Pキーを押して、どのように表示されるかを確認できます。
ただし、フィードバックループは通常ほど速くはありません。
コードを印刷用に適合させるには、WebページとPDFの2つの主な違いを回避する必要があります。
Reactを使用してSPAの「印刷可能なバージョン」を作成します。ページの印刷可能なバージョンを作成するには、ページを構成するさまざまなコンポーネントを追加/削除/変更する必要があります。
この部分には基本的に2つの解決策があります。
2番目のソリューション(より安価)を選択した場合は、既存のコンポーネントを適応させる必要があります。たとえば、3つのタブがあるテーブルがある場合、すべてのタブのコンテンツを表示したい場合があります。タブを次々に表示するようなものでうまくいくかもしれません:
動的のみ:
<Table selectedTabIndex="tab1" />
動的および静的:
const tabNames = ['tab1', 'tab2', 'tab3']
(isPrintable ?
tabNames.map(tabName => <Table key={tabName} selectedTab={tabName} /> :
<Table selectedTabIndex='tab1' />
);
この場合、isPrintable
小道具は3つのタブを表示するか、最初のタブだけを表示するかを決定します。この小道具は、印刷用に調整する必要があるページのすべての動的コンポーネントに渡すことができます。
Boursoramaの例でわかるように、ページを印刷しようとすると、コンポーネントが2ページの間で途切れる場合があります。あなたが彼に言わなければあなたのウェブブラウザがどこでページを壊すかを知らないのでそれは起こります。これは、break-inside
CSSプロパティがステップインする場所です。明らかに、以前のタブのセットが途中で途切れることは望ましくありません。グラフもページ上のほとんどすべてのコンポーネントもありません。次に、このCSSプロパティを追加するために、前のコードを適応させる必要があります。style={{ breakInside: 'avoid' }}
inline-cssで機能しますが、jsx/tsxファイルのどこにでも追加したくない場合があります。
むしろスタイルシートを使用したいと思います。また、既存のすべてのCSSクラスにこのプロパティを追加する代わりに、このmedia @print
オプションを使用することをお勧めします。これにより、印刷専用にWebページをカスタマイズできます。たとえば、美的理由や利便性のために、テキストを少し大きくしたり、印刷可能なバージョンで滑らかな灰色にしたりすることができます。
これをcssファイルに追加し@media object
ます。
media @print {
body: {
font-size: "16px";
color: "lightgrey";
}
.no-break-inside {
// apply this class to every component that shouldn't be cut off between to pages of your PDF
break-inside: "avoid";
}
.break-before {
// apply this class to every component that should always display on next page
break-before: "always";
}
}
<MyComponent isPrintable=true className="no-break-inside" />
これらのいくつかのCSSのヒントは、Webページのレンダリングを大幅に改善するのに役立ちます。
これで、ページを印刷する準備が整いました。isPrintable
小道具をページに渡し、ブラウザで右クリックして印刷すると、それがわかります。表示されている内容に非常に満足しています。これが印刷の一部です。これで、印刷可能なWebページができましたが、ユーザーはそれを認識できず、WebサイトでCtrl + Pを押しても、Webページの「動的」バージョンが表示されます。彼らにPDFバージョンを生成させ、最新の生成を自動化させるには、PDFサーバー側を直接生成するボタンを追加し、さらにカスタマイズを追加することもできます。これは、とりわけ、Puppeteerが使用されるものです。
Puppeteerは、Chromeを制御するための一般的で自然な方法です。ブラウザ機能へのフルアクセスを提供し、最も重要なこととして、リモートサーバー上で完全にヘッドレスモードでChromeを実行できます[...]
—Dima Bekerman、https://www.imperva.com/blog/headless-chrome-devops-love-it-so-do-hackers-heres-why/
![]() |
---|
Puppeteerがサーバー側でどのように機能するかのスキーマ |
Reactアプリの生成は、Webブラウザーによって行われます。DOMをレンダリングするためにJavaScriptを実行できる最小限の環境が必要です。Puppeteerは、ヘッドレスクロムを発売することでそれを実現します。今後、生成はサーバー上で行われるため、Webブラウザーにグラフィカルユーザーインターフェイス(GUI)を設定する必要はありません。Chromium withは、印刷可能なバージョンを生成します。ユーザーがWebブラウザーに表示するのと同じページですが、isPrintable
小道具がアクティブになっています。次に、Puppeteerはpdf
、ページの印刷をトリガーするいくつかのカスタムオプションを使用して、ページで関数を実行します。
プリンタサービスを呼び出すURLのボタンを追加するだけです。
<Button onClick={window.open(downloadUrl, "_blank")}>Download as PDF</Button>
これdownloadUrl
は実際にはサーバーでのGETリクエストであり、サーバー上でPuppeteerを実行し、コンテンツタイプのコンテンツを返します。application/pdf
では、このPuppeteerコードはどのように見えますか?
実際にPDFをダウンロードできるようにするには、数行のコード行が必要です。
最小のコードは次のようになります。
const puppeteer = require("puppeteer");
(async () => {
const browser = await puppeteer.launch(); // launch a browser (chromium by default but you can chose another one)
const page = await browser.newPage(); // open a page in the browser
await page.goto("https://printable-version-of-my-wbe-page.com", {
waitUntil: "networkidle2",
}); // visit the printable version of your page
await page.pdf({ format: "a4", path: "./my_file.pdf" }); // generate the PDF 🎉
await browser.close(); // don't forget to close the browser. Otherwise, it may cause performances issues or the server may even crash..
})();
これらは、PDFを生成するために必要な一般的な手順です。バックエンドによっては、PDFをサーバーにダウンロードするのではなく、応答オブジェクトにレンダリングしてクライアント(ユーザーのWebブラウザー)に送り返すことをお勧めします。page.pdf()
次に、メソッドを適応させ、ユーザーがブラウザーで開いconst buffer = await page.pdf({ format: 'a4'});
たページでこのバッファーを返し_blank
、応答を待つ必要があります。
もちろん、公式ドキュメントの助けを借りて、用紙サイズ、スケール、マージンなど、ブラウザに自然にあるオプションを適応させることができます:https ://github.com/puppeteer/puppeteer/blob/v10 .4.0 / docs / api.md#pagepdfoptions。
私がお勧めするクールなオプションの1つは、主にGoogle Chromeによって提供されるデフォルトのオプションが本当に醜いため、ヘッダーまたはフッターのテンプレートです。HTMLファイルテンプレートを読んで、現在の日付、各ページのページ番号、さらには画像/ロゴなど、表示するデータに渡すだけです。
const footerBase = fs.readFileSync("./footer.html", "utf8");
customFooter = footerBase
.replace("{{date}}", new Date())
.replace("{{image_data}}", imageData);
await page.pdf({ format: "a4", footerTemplate: customFooter });
HTMLテンプレートを使用する
<style>
#logo {
height: 40px;
content: url("data:image/png;base64,{{image_data}}");
}
</style>
<div id="body">
<div id="page-number-paragraph">
<span id="date">{{date}}</span>
<span>Page</span>
<span class="pageNumber"/></span>
<span>/</span>
<span class="totalPages"></span>
</div>
<div id="brand-container">
<span id="logo"></span>
</div>
</div>
これで、完全にカスタマイズされたフッターがPDFに提供されました。
PDF生成に関しては他にも多くのオプションがありますが、ブラウザを起動し、新しいページを開き、URLに移動する前の手順でも、サーバー上でPDF生成を完全にカスタマイズできます。
最後に、React / CSSコードを適応させ、Puppeteerを使用することで、ページの完全なカスタムPDFを簡単に提供できます。さらに、Puppeteerはすべてのものをサーバー側で実行しています。これにより、この機能は完全に透過的になり、エンドユーザーにとっては非常に高速になり、どのブラウザのすべてのユーザーに対しても同じ結果が得られます。Puppeteerは非常に強力で、開発者がPDFを非常に簡単に生成できるようにする多くのオプションがあり、ユーザーのブラウザのデフォルトよりもはるかにカスタムで美しいレンダリングが可能です。
出典:https ://blog.theodo.com/2021/10/pdf-generation-react-puppeteer/
1659105180
Recentemente, tive que fornecer uma nova funcionalidade no meu projeto: a de "baixar como PDF". A primeira coisa que me perguntei foi por que deveríamos fornecer essa funcionalidade? Já não existe nativamente com praticamente todos os navegadores da Web com a opção de clicar com o botão direito do mouse / imprimir / "salvar como pdf"? Bem, eu tentei na minha página da web e o resultado foi realmente decepcionante:
Exemplo em uma página aleatória do Boursorama , que se parece com:
Renderização:
Foi aí que eu disse "ok esse recurso pode valer a pena", mas como devo fazer? Existem muitas bibliotecas de código aberto que podem gerar PDFs. Mas minha escolha foi naturalmente para a conhecida biblioteca desenvolvida pelo Google: Puppeteer. Para mim, é a maneira mais fácil de lidar com a geração de PDF de aplicativos de página única. Pode não ser assim se você não lida com pacotes de javascript, mas com HTML/CSS simples. De fato, existem soluções mais fáceis para este caso de uso, como wkhtmltopdf, html-pdf-node ou jspdf, por exemplo.
Neste artigo, quero dar algumas dicas para gerar belos PDFs de SPAs com o Puppeteer. Em primeiro lugar, vou explicar como você pode criar uma versão para impressão da sua página com React e Puppeteer. Em seguida, mostrarei como usar o Puppeteer para a geração de sua nova página imprimível.
Para esta parte, você não precisa ter o Puppeteer ou qualquer outro serviço de impressora configurado. Você pode fazer suas alterações em seu código como de costume e, em seguida, ctrl+P em sua página para ver como fica:
No entanto, o ciclo de feedback não é tão rápido quanto de costume.
Para adaptar seu código para impressão, você deve ignorar as 2 principais diferenças entre uma página da Web e um PDF:
Crie a "versão para impressão" do seu SPA com React. Para criar a versão imprimível de nossa página, você terá que adicionar/remover/modificar os diferentes componentes que compõem a página.
Você basicamente tem 2 soluções para esta parte:
Se optar pela segunda solução (que é menos dispendiosa), terá de adaptar os seus componentes existentes. Por exemplo, se você tiver uma tabela com 3 guias, provavelmente desejará exibir o conteúdo de todas as guias. Algo como exibir as guias uma após a outra pode resolver o problema:
Apenas dinâmico:
<Table selectedTabIndex="tab1" />
Dinâmico e Estático:
const tabNames = ['tab1', 'tab2', 'tab3']
(isPrintable ?
tabNames.map(tabName => <Table key={tabName} selectedTab={tabName} /> :
<Table selectedTabIndex='tab1' />
);
Nesse caso, os isPrintable
adereços determinarão se serão exibidas as 3 abas ou apenas a primeira. Você pode passar esses adereços para todos os componentes dinâmicos da sua página, que precisam ser adaptados para impressão.
Como você pode ver com o exemplo do Boursorama, seus componentes podem ser cortados entre 2 páginas ao tentar imprimir sua página. Isso acontece porque seu navegador da web não tem ideia de onde quebrar a página se você não contar a ele. É aqui que break-inside
entra a propriedade CSS. Você obviamente não quer que seu conjunto anterior de abas seja cortado no meio. Nem seus gráficos ou quase qualquer componente em sua página. Então você teria que adaptar o código anterior para adicionar essa propriedade CSS. Funcionaria com inline-css, mas você provavelmente não deseja adicionar todos os style={{ breakInside: 'avoid' }}
lugares em seus arquivos jsx/tsx.
Você prefere usar folhas de estilo. E ao invés de adicionar esta propriedade em cada classe CSS já existente, você vai querer usar a media @print
opção. Isso permitirá que você personalize sua página da Web apenas para impressão! Por exemplo, você pode querer que seu texto seja um pouco maior ou tenha uma cor cinza suave na versão para impressão, por qualquer razão estética ou conveniência.
Vamos apenas adicionar isso no @media object
seu arquivo css:
media @print {
body: {
font-size: "16px";
color: "lightgrey";
}
.no-break-inside {
// apply this class to every component that shouldn't be cut off between to pages of your PDF
break-inside: "avoid";
}
.break-before {
// apply this class to every component that should always display on next page
break-before: "always";
}
}
<MyComponent isPrintable=true className="no-break-inside" />
Essas poucas dicas de CSS devem te ajudar a melhorar muito a renderização da sua página.
Agora, sua página está pronta para impressão. Você sabe quando passa os isPrintable
adereços para sua página, clica com o botão direito + imprimir no seu navegador, e fica bastante confortável com o que vê. Aí vem a parte da impressão. Agora você tem uma versão para impressão da sua página da web, mas os usuários não têm ideia disso e, mesmo que o ctrl + P no site, eles vejam a versão "dinâmica" da página da web. Para permitir que eles gerem a versão PDF e automatizem a geração da mais recente, você provavelmente deseja adicionar um botão que gere diretamente o lado do servidor PDF e até adicione alguma personalização. É para isso que, entre outras coisas, o Puppeteer é usado.
O Puppeteer é uma maneira comum e natural de controlar o Chrome. Ele fornece acesso total aos recursos do navegador e, mais importante, pode executar o Chrome no modo totalmente sem periféricos em um servidor remoto [...]
—Dima Bekerman, https://www.imperva.com/blog/headless-chrome-devops-love-it-so-do-hackers-heres-why/
![]() |
---|
Esquema de como o Puppeteer funciona no lado do servidor |
A geração do aplicativo React é feita por um navegador da web. Precisamos do ambiente mínimo capaz de executar javascript para renderizar um DOM. O Puppeteer fará isso lançando um cromo sem cabeça. A partir de agora, e uma vez que a geração é feita no servidor, o navegador web não necessita de ter uma interface gráfica de utilizador (GUI). O Chromium gera a versão para impressão: a mesma página que o usuário vê em seu navegador, mas com os isPrintable
adereços ativados. Em seguida, o Puppeteer executará a pdf
função na página com algumas opções personalizadas que acionarão a impressão da página.
Basta adicionar o botão com a URL que chama o serviço de impressora:
<Button onClick={window.open(downloadUrl, "_blank")}>Download as PDF</Button>
Na downloadUrl
verdade, é uma solicitação GET em seu servidor que executará o Puppeteer no servidor e retornará conteúdo com tipo de conteúdoapplication/pdf
Então, como é esse código do Puppeteer?
Para poder realmente baixar o PDF, você só precisa de algumas linhas de código.
O código mínimo ficaria assim:
const puppeteer = require("puppeteer");
(async () => {
const browser = await puppeteer.launch(); // launch a browser (chromium by default but you can chose another one)
const page = await browser.newPage(); // open a page in the browser
await page.goto("https://printable-version-of-my-wbe-page.com", {
waitUntil: "networkidle2",
}); // visit the printable version of your page
await page.pdf({ format: "a4", path: "./my_file.pdf" }); // generate the PDF 🎉
await browser.close(); // don't forget to close the browser. Otherwise, it may cause performances issues or the server may even crash..
})();
Estas são as etapas comuns necessárias para gerar o PDF. Dependendo do seu backend, você provavelmente não quer baixar o PDF no servidor, mas renderizá-lo em um objeto de resposta, para enviá-lo de volta ao cliente (o navegador da web do usuário). Você deve então adaptar o page.pdf()
método const buffer = await page.pdf({ format: 'a4'});
e retornar esse buffer na _blank
página que o usuário abriu em seu navegador, aguardando uma resposta.
Claro que você pode adaptar as opções que você naturalmente tem no seu navegador, como o tamanho do papel, a escala, as margens, etc. com a ajuda da documentação oficial: https://github.com/puppeteer/puppeteer/blob/v10 .4.0/docs/api.md#pagepdfoptions .
Uma opção legal que eu recomendo, principalmente porque o padrão fornecido pelo Google Chrome é muito feio, é o modelo de cabeçalho ou rodapé. Basta ler um modelo de arquivo HTML e passá-lo pelos dados que deseja exibir, como a data atual, o número da página de cada página ou até mesmo uma imagem/logotipo:
const footerBase = fs.readFileSync("./footer.html", "utf8");
customFooter = footerBase
.replace("{{date}}", new Date())
.replace("{{image_data}}", imageData);
await page.pdf({ format: "a4", footerTemplate: customFooter });
usando um modelo html
<style>
#logo {
height: 40px;
content: url("data:image/png;base64,{{image_data}}");
}
</style>
<div id="body">
<div id="page-number-paragraph">
<span id="date">{{date}}</span>
<span>Page</span>
<span class="pageNumber"/></span>
<span>/</span>
<span class="totalPages"></span>
</div>
<div id="brand-container">
<span id="logo"></span>
</div>
</div>
Agora você forneceu ao seu PDF um rodapé totalmente personalizado.
Existem muitas outras opções em relação à geração de PDF, mas também para as etapas anteriores de iniciar o navegador, abrir uma nova página, acessar a URL, que permitirá personalizar totalmente sua geração de PDF no servidor.
Finalmente, adaptando seu código React/CSS e usando o Puppeteer, você pode facilmente fornecer um PDF totalmente personalizado de sua página. Além disso, o Puppeteer está fazendo tudo do lado do servidor. O que torna esse recurso totalmente transparente, bastante rápido para o usuário final e com o mesmo resultado para todos os usuários em qualquer navegador! O Puppeteer é realmente poderoso e possui muitas opções que tornam a geração de PDF bastante fácil para os desenvolvedores, e com uma renderização muito mais personalizada e bonita do que a padrão nos navegadores dos usuários.
Fonte: https://blog.theodo.com/2021/10/pdf-generation-react-puppeteer/
1659026160
我最近不得不在我的项目中提供一项新功能:“下载为 PDF”功能。我想的第一件事是我们为什么要提供这个功能?它不是已经原生存在于几乎所有具有鼠标右键单击/打印/“另存为 pdf”选项的 Web 浏览器中吗?好吧,我在我的网页上试过,结果真的很令人失望:
Boursorama 随机页面上的示例,如下所示:
渲染:
那时我说“好吧,这个功能可能值得”,但我该怎么做呢?有许多可以生成 PDF 的开源库。但我自然而然地选择了著名的 google 开发的库:Puppeteer。在我看来,这是处理单页应用程序的 PDF 生成最简单的方法。如果您不处理 javascript 包而是使用纯 HTML/CSS,则可能并非如此。实际上,对于这种用例,有更简单的解决方案,例如 wkhtmltopdf、html-pdf-node 或 jspdf。
在本文中,我想为您提供一些使用 Puppeteer 生成漂亮的 SPA PDF 的技巧。首先,我将向您解释如何使用 React 和 Puppeteer 创建页面的可打印版本。然后,我将向您展示如何使用 Puppeteer 生成新的可打印页面。
对于这部分,您实际上不需要设置 Puppeteer 或任何其他打印机服务。您可以像往常一样对代码进行更改,然后在您的页面上按 ctrl+P 来查看它的样子:
然而,反馈循环并不像往常那样快。
要使您的代码适应打印,您必须绕过网页和 PDF 之间的 2 个主要区别:
使用React创建 SPA 的“可打印版本” 。要创建我们页面的可打印版本,您必须添加/删除/修改构成页面的不同组件。
对于这部分,您基本上有 2 个解决方案:
如果您选择第二种解决方案(成本更低),您将不得不调整现有组件。例如,如果您有一个包含 3 个选项卡的表格,您可能希望显示所有选项卡的内容。诸如一个接一个地显示选项卡之类的东西可能会起作用:
只有动态:
<Table selectedTabIndex="tab1" />
动态和静态:
const tabNames = ['tab1', 'tab2', 'tab3']
(isPrintable ?
tabNames.map(tabName => <Table key={tabName} selectedTab={tabName} /> :
<Table selectedTabIndex='tab1' />
);
在这种情况下,isPrintable
道具将决定是显示 3 个选项卡,还是只显示第一个。您可以将此道具传递给页面的每个动态组件,这些组件需要适应打印。
正如您在 Boursorama 示例中看到的那样,在尝试打印页面时,您的组件可能会在 2 页之间被切断。发生这种情况是因为如果您不告诉他,您的网络浏览器不知道在哪里中断页面。这就是break-inside
CSS 属性介入的地方。您显然不希望之前的一组选项卡在中间被切断。您的图表或页面上的几乎任何组件都不是。然后,您必须修改前面的代码来添加这个 CSS 属性。它可以与 inline-css 一起使用,但您可能不想style={{ breakInside: 'avoid' }}
在 jsx/tsx 文件中添加到处。
您宁愿使用样式表。而不是在每个已经存在的 CSS 类上添加这个属性,你会想要使用这个media @print
选项。这将让您自定义您的网页仅用于打印!例如,出于任何美学原因或方便,您可能希望文本更大一点或在可打印版本上具有平滑的灰色。
我们只需将其添加到@media object
您的 css 文件中:
media @print {
body: {
font-size: "16px";
color: "lightgrey";
}
.no-break-inside {
// apply this class to every component that shouldn't be cut off between to pages of your PDF
break-inside: "avoid";
}
.break-before {
// apply this class to every component that should always display on next page
break-before: "always";
}
}
<MyComponent isPrintable=true className="no-break-inside" />
这几个 CSS 技巧应该可以帮助您改善网页的渲染。
现在,您的页面已准备好打印。当您将isPrintable
道具传递到您的页面时,您就知道了,在浏览器上右键单击 + 打印,并且您对所看到的内容感到非常满意。这里是打印的部分。您现在有了网页的可打印版本,但用户不知道它,即使在网站上按 ctrl + P,他们也会看到网页的“动态”版本。为了让他们生成 PDF 版本并自动生成最新版本,您可能想要添加一个按钮,直接生成 PDF 服务器端,甚至添加一些自定义。这就是 Puppeteer 的用途。
Puppeteer 是控制 Chrome 的一种常见且自然的方式。它提供对浏览器功能的完全访问,最重要的是,可以在远程服务器上以完全无头模式运行 Chrome [...]
——Dima Bekerman,https://www.imperva.com/blog/headless-chrome-devops-love-it-so-do-hackers-heres-why/
![]() |
---|
Puppeteer 如何在服务器端工作的架构 |
React 应用程序的生成由 Web 浏览器完成。我们需要能够执行 javascript 来呈现 DOM 的最小环境。Puppeteer 将通过发射无头铬来做到这一点。从现在开始,由于生成是在服务器上完成的,Web 浏览器不需要图形用户界面 (GUI)。Chromium 生成可打印版本:用户在其网络浏览器上看到的相同页面,但isPrintable
激活了道具。然后 Puppeteer 将pdf
使用一些自定义选项在页面上执行该功能,这些选项将触发页面的打印。
只需添加带有调用打印机服务的 URL 的按钮:
<Button onClick={window.open(downloadUrl, "_blank")}>Download as PDF</Button>
这downloadUrl
实际上是您服务器上的 GET 请求,它将在服务器上执行 Puppeteer 并返回具有 content-type 的内容application/pdf
那么这个 Puppeteer 代码是什么样的呢?
为了能够实际下载 PDF,您只需要几行代码。
最小的代码将如下所示:
const puppeteer = require("puppeteer");
(async () => {
const browser = await puppeteer.launch(); // launch a browser (chromium by default but you can chose another one)
const page = await browser.newPage(); // open a page in the browser
await page.goto("https://printable-version-of-my-wbe-page.com", {
waitUntil: "networkidle2",
}); // visit the printable version of your page
await page.pdf({ format: "a4", path: "./my_file.pdf" }); // generate the PDF 🎉
await browser.close(); // don't forget to close the browser. Otherwise, it may cause performances issues or the server may even crash..
})();
这些是生成 PDF 所需的常见步骤。根据您的后端,您可能不想在服务器上下载 PDF,而是在响应对象上呈现它,然后将其发送回客户端(用户的 Web 浏览器)。然后,您应该调整该page.pdf()
方法const buffer = await page.pdf({ format: 'a4'});
并在用户在其浏览器上打开的页面上返回此缓冲区_blank
,等待响应。
您当然可以在官方文档的帮助下调整您在浏览器上自然拥有的选项,例如纸张大小、比例、边距等:https ://github.com/puppeteer/puppeteer/blob/v10 .4.0/docs/api.md#pagepdfoptions。
我推荐的一个很酷的选项是页眉或页脚模板,主要是因为谷歌浏览器提供的默认选项真的很难看。只需读取 HTML 文件模板并将其传递给您要显示的数据,例如当前日期、每页的页码,甚至是图像/徽标:
const footerBase = fs.readFileSync("./footer.html", "utf8");
customFooter = footerBase
.replace("{{date}}", new Date())
.replace("{{image_data}}", imageData);
await page.pdf({ format: "a4", footerTemplate: customFooter });
使用 html 模板
<style>
#logo {
height: 40px;
content: url("data:image/png;base64,{{image_data}}");
}
</style>
<div id="body">
<div id="page-number-paragraph">
<span id="date">{{date}}</span>
<span>Page</span>
<span class="pageNumber"/></span>
<span>/</span>
<span class="totalPages"></span>
</div>
<div id="brand-container">
<span id="logo"></span>
</div>
</div>
您现在已经为您的 PDF 提供了一个完全自定义的页脚。
关于 PDF 生成,还有很多其他选项,还有启动浏览器、打开新页面、转到 URL 的前面步骤,这将让您完全自定义服务器上的 PDF 生成。
最后,通过调整您的 React/CSS 代码并使用 Puppeteer,您可以轻松地为您的页面提供完全自定义的 PDF。此外,Puppeteer 正在做服务器端的所有工作。这使得此功能完全透明,对最终用户来说非常快,并且对于任何浏览器上的每个用户都具有相同的结果!Puppeteer 非常强大,并且有很多选项可以让开发人员轻松生成 PDF,并且渲染比用户浏览器上的默认渲染更加自定义和美观。
来源:https ://blog.theodo.com/2021/10/pdf-generation-react-puppeteer/
1659022266
J'ai récemment dû fournir une nouvelle fonctionnalité sur mon projet : celle du "téléchargement en PDF". La première chose que je me suis demandé était pourquoi devrions-nous fournir cette fonctionnalité ? N'existe-t-il pas déjà nativement avec à peu près tous les navigateurs Web avec une option clic droit / imprimer / "enregistrer en pdf" ? Eh bien, j'ai essayé sur ma page Web et le résultat était vraiment décevant:
Exemple sur une page aléatoire Boursorama , qui ressemble à :
le rendu:
C'est alors que j'ai dit "ok cette fonctionnalité en vaut peut-être la peine", mais comment dois-je faire ? Il existe de nombreuses bibliothèques open source capables de générer des PDF. Mais mon choix s'est porté naturellement sur la bibliothèque bien connue développée par google : Puppeteer. Selon moi, c'est le moyen le plus simple de gérer la génération PDF d'applications à page unique. Ce n'est peut-être pas le cas si vous ne traitez pas avec des bundles javascript mais avec du HTML/CSS brut. En effet, il existe des solutions plus simples pour ce cas d'utilisation comme wkhtmltopdf, html-pdf-node ou jspdf par exemple.
Dans cet article, je souhaite vous donner quelques astuces pour générer de beaux PDF de SPA avec Puppeteer. Dans un premier temps, je vais vous expliquer comment vous pouvez créer une version imprimable de votre page avec React et Puppeteer. Ensuite, je vous montrerai comment utiliser Puppeteer pour la génération de votre nouvelle page imprimable.
Pour cette partie, vous n'avez pas réellement besoin d'avoir Puppeteer ou tout autre service d'impression configuré. Vous pouvez apporter vos modifications à votre code comme d'habitude, puis ctrl+P sur votre page pour voir à quoi il ressemble :
Cependant, la boucle de rétroaction n'est pas aussi rapide que d'habitude.
Pour adapter votre code à l'impression, vous devez contourner les 2 principales différences entre une page web et un PDF :
Créez la "version imprimable" de votre SPA avec React. Pour créer la version imprimable de notre page, vous devrez ajouter/supprimer/modifier les différents éléments qui composent la page.
Vous avez essentiellement 2 solutions pour cette partie :
Si vous optez pour la deuxième solution (moins coûteuse), vous devrez adapter vos composants existants. Par exemple, si vous avez un tableau avec 3 onglets, vous souhaiterez probablement afficher le contenu de tous les onglets. Quelque chose comme afficher les onglets les uns après les autres peut faire l'affaire :
Uniquement dynamique :
<Table selectedTabIndex="tab1" />
Dynamique et Statique :
const tabNames = ['tab1', 'tab2', 'tab3']
(isPrintable ?
tabNames.map(tabName => <Table key={tabName} selectedTab={tabName} /> :
<Table selectedTabIndex='tab1' />
);
Dans ce cas, les isPrintable
props détermineront s'il faut afficher les 3 onglets, ou juste le premier. Vous pouvez transmettre ces accessoires à chaque composant dynamique de votre page, qui doit être adapté pour l'impression.
Comme vous pouvez le voir avec l'exemple Boursorama, vos composants peuvent être coupés entre 2 pages lorsque vous essayez d'imprimer votre page. Cela se produit parce que votre navigateur Web n'a aucune idée de l'endroit où couper la page si vous ne le lui dites pas. C'est là qu'intervient la break-inside
propriété CSS. Vous ne voulez évidemment pas que votre ensemble d'onglets précédent soit coupé au milieu. Ni vos graphiques ni presque aucun composant de votre page. Ensuite, vous devrez adapter le code précédent pour ajouter cette propriété CSS. Cela fonctionnerait avec inline-css mais vous ne voulez probablement pas ajouter le style={{ breakInside: 'avoid' }}
partout dans vos fichiers jsx/tsx.
Vous préférez utiliser des feuilles de style. Et au lieu d'ajouter cette propriété sur chaque classe CSS déjà existante, vous voudrez utiliser l' media @print
option. Cela vous permettra de personnaliser votre page Web pour l'impression uniquement ! Par exemple, vous voudrez peut-être que votre texte soit un peu plus grand ou qu'il ait une couleur grise lisse sur la version imprimable, pour une raison esthétique ou de commodité.
Nous allons simplement ajouter ceci @media object
dans votre fichier CSS :
media @print {
body: {
font-size: "16px";
color: "lightgrey";
}
.no-break-inside {
// apply this class to every component that shouldn't be cut off between to pages of your PDF
break-inside: "avoid";
}
.break-before {
// apply this class to every component that should always display on next page
break-before: "always";
}
}
<MyComponent isPrintable=true className="no-break-inside" />
Ces quelques astuces CSS devraient vous aider à améliorer considérablement le rendu de votre page web.
Maintenant, votre page est prête à être imprimée. Vous le savez lorsque vous passez les isPrintable
accessoires à votre page, faites un clic droit + imprimer sur votre navigateur, et vous êtes assez à l'aise avec ce que vous voyez. Voici la partie de l'impression. Vous avez maintenant une version imprimable de votre page Web, mais les utilisateurs n'en ont aucune idée, et même si le ctrl + P sur le site Web, ils verront la version "dynamique" de la page Web. Pour leur permettre de générer la version PDF et d'automatiser la génération de la dernière, vous souhaitez probablement ajouter un bouton qui générera directement le PDF côté serveur, et même ajouter quelques personnalisations. C'est à cela, entre autres, que Marionnettiste sert.
Puppeteer est un moyen courant et naturel de contrôler Chrome. Il offre un accès complet aux fonctionnalités du navigateur et, plus important encore, peut exécuter Chrome en mode entièrement sans tête sur un serveur distant [...]
—Dima Bekerman, https://www.imperva.com/blog/headless-chrome-devops-love-it-so-do-hackers-heres-why/
![]() |
---|
Schéma du fonctionnement de Puppeteer côté serveur |
La génération de l'application React est effectuée par un navigateur Web. Nous avons besoin d'un environnement minimal capable d'exécuter du javascript pour rendre un DOM. Marionnettiste le fera en lançant un chrome sans tête. Désormais, et puisque la génération se fait sur le serveur, le navigateur web n'a plus besoin d'avoir une interface utilisateur graphique (GUI). Chromium avec générer la version imprimable : la même page que l'utilisateur voit sur son navigateur Web mais avec les isPrintable
accessoires activés. Ensuite, Puppeteer exécutera la pdf
fonction sur la page avec des options personnalisées qui déclencheront l'impression de la page.
Ajoutez simplement le bouton avec l'URL qui appelle le service d'impression :
<Button onClick={window.open(downloadUrl, "_blank")}>Download as PDF</Button>
Il downloadUrl
s'agit en fait d'une requête GET sur votre serveur qui exécutera Puppeteer sur le serveur et renverra le contenu avec le type de contenuapplication/pdf
Alors, à quoi ressemble ce code Puppeteer ?
Pour pouvoir réellement télécharger le PDF, il vous suffit de quelques lignes de code.
Le code minimal ressemblerait alors à :
const puppeteer = require("puppeteer");
(async () => {
const browser = await puppeteer.launch(); // launch a browser (chromium by default but you can chose another one)
const page = await browser.newPage(); // open a page in the browser
await page.goto("https://printable-version-of-my-wbe-page.com", {
waitUntil: "networkidle2",
}); // visit the printable version of your page
await page.pdf({ format: "a4", path: "./my_file.pdf" }); // generate the PDF 🎉
await browser.close(); // don't forget to close the browser. Otherwise, it may cause performances issues or the server may even crash..
})();
Ce sont les étapes courantes dont vous aurez besoin pour générer le PDF. En fonction de votre backend, vous ne souhaitez probablement pas ne pas télécharger le PDF sur le serveur mais le rendre sur un objet de réponse, pour le renvoyer au client (le navigateur Web de l'utilisateur). Il faut ensuite adapter la page.pdf()
méthode avec const buffer = await page.pdf({ format: 'a4'});
et renvoyer ce tampon sur la _blank
page que l'utilisateur a ouverte sur son navigateur, en attente d'une réponse.
Vous pouvez bien sûr adapter les options dont vous disposez naturellement sur votre navigateur, comme la taille du papier, l'échelle, les marges, etc. à l'aide de la documentation officielle : https://github.com/puppeteer/puppeteer/blob/v10 .4.0/docs/api.md#pagepdfoptions .
Une option intéressante que je recommande, principalement parce que celle par défaut fournie par Google Chrome est vraiment moche, est le modèle d'en-tête ou de pied de page. Il vous suffit de lire un modèle de fichier HTML et de lui transmettre les données que vous souhaitez afficher, telles que la date actuelle, le numéro de page pour chaque page ou même une image/logo :
const footerBase = fs.readFileSync("./footer.html", "utf8");
customFooter = footerBase
.replace("{{date}}", new Date())
.replace("{{image_data}}", imageData);
await page.pdf({ format: "a4", footerTemplate: customFooter });
à l'aide d'un modèle html
<style>
#logo {
height: 40px;
content: url("data:image/png;base64,{{image_data}}");
}
</style>
<div id="body">
<div id="page-number-paragraph">
<span id="date">{{date}}</span>
<span>Page</span>
<span class="pageNumber"/></span>
<span>/</span>
<span class="totalPages"></span>
</div>
<div id="brand-container">
<span id="logo"></span>
</div>
</div>
Vous avez maintenant fourni à votre PDF un pied de page entièrement personnalisé.
Il existe de nombreuses autres options concernant, la génération de PDF, mais aussi pour les étapes précédentes de lancement du navigateur, d'ouverture d'une nouvelle page, d'aller à l'URL, qui vous permettront de personnaliser entièrement votre génération de PDF sur le serveur.
Enfin, en adaptant votre code React/CSS et en utilisant Puppeteer, vous pouvez facilement fournir un PDF entièrement personnalisé de votre page. De plus, Puppeteer fait tout le travail côté serveur. Ce qui rend cette fonctionnalité totalement transparente, assez rapide pour l'utilisateur final, et avec le même résultat pour chaque utilisateur sur n'importe quel navigateur ! Puppeteer est vraiment puissant et possède de nombreuses options qui rendent la génération de PDF assez facile pour les développeurs, et avec un rendu beaucoup plus personnalisé et beau que celui par défaut sur les navigateurs des utilisateurs.
Source : https://blog.theodo.com/2021/10/pdf-generation-react-puppeteer/
1659020400
Recientemente tuve que proporcionar una nueva funcionalidad en mi proyecto: la de "descargar como PDF". Lo primero que me pregunté fue ¿por qué deberíamos proporcionar esta funcionalidad? ¿No existe ya de forma nativa con casi todos los navegadores web con la opción de clic derecho del mouse / imprimir / "guardar como pdf"? Bueno, probé en mi página web y el resultado fue realmente decepcionante:
Ejemplo en una página aleatoria de Boursorama , que se parece a:
representación:
Fue entonces cuando dije "bien, esta función puede valer la pena", pero ¿cómo debo hacerlo? Hay muchas bibliotecas de código abierto que pueden generar archivos PDF. Pero mi elección fue naturalmente a la conocida biblioteca desarrollada por Google: Titiritero. Según yo, es la forma más fácil de lidiar con la generación de PDF de aplicaciones de una sola página. Puede que no sea así si no trabaja con paquetes de JavaScript sino con HTML/CSS simple. De hecho, existen soluciones más sencillas para este caso de uso, como wkhtmltopdf, html-pdf-node o jspdf, por ejemplo.
En este artículo, quiero darte algunos consejos para generar hermosos PDF de SPA con Puppeteer. En primer lugar, te explicaré cómo puedes crear una versión imprimible de tu página con React y Puppeteer. Luego, te mostraré cómo usar Titiritero para la generación de tu nueva página imprimible.
Para esta parte, en realidad no necesita tener Puppeteer ni ningún otro servicio de impresión configurado. Puede realizar los cambios en su código como de costumbre, y luego ctrl+P en su página para ver cómo se ve:
Sin embargo, el circuito de retroalimentación no es tan rápido como de costumbre.
Para adaptar su código para la impresión, debe omitir las 2 diferencias principales entre una página web y un PDF:
Crea la "versión imprimible" de tu SPA con React. Para crear la versión imprimible de nuestra página, deberá agregar/eliminar/modificar los diferentes componentes que componen la página.
Básicamente tienes 2 soluciones para esta parte:
Si opta por la segunda solución (que es menos costosa), deberá adaptar sus componentes existentes. Por ejemplo, si tiene una tabla con 3 pestañas, probablemente querrá mostrar el contenido de todas las pestañas. Algo así como mostrar las pestañas una tras otra puede funcionar:
Solo dinámico:
<Table selectedTabIndex="tab1" />
Dinámico y Estático:
const tabNames = ['tab1', 'tab2', 'tab3']
(isPrintable ?
tabNames.map(tabName => <Table key={tabName} selectedTab={tabName} /> :
<Table selectedTabIndex='tab1' />
);
En este caso, los isPrintable
accesorios determinarán si mostrar las 3 pestañas o solo la primera. Puede pasar estos accesorios a cada componente dinámico de su página, que necesita ser adaptado para la impresión.
Como puede ver con el ejemplo de Boursorama, sus componentes pueden cortarse entre 2 páginas al intentar imprimir su página. Sucede porque su navegador web no tiene idea de dónde romper la página si no se lo dice. Aquí es donde break-inside
interviene la propiedad CSS. Obviamente, no desea que su conjunto anterior de pestañas se corte en el medio. Ni sus gráficos ni casi ningún componente de su página. Entonces tendrías que adaptar el código anterior para agregar esta propiedad CSS. Funcionaría con inline-css, pero probablemente no desee agregarlo style={{ breakInside: 'avoid' }}
en todas partes en sus archivos jsx/tsx.
Preferirías usar hojas de estilo. Y en lugar de agregar esta propiedad en cada clase CSS ya existente, querrá usar la media @print
opción. ¡Esto le permitirá personalizar su página web solo para imprimir! Por ejemplo, puede querer que su texto sea un poco más grande o que tenga un color gris suave en la versión imprimible, por cualquier razón estética o conveniencia.
Simplemente agregaremos esto en @media object
su archivo css:
media @print {
body: {
font-size: "16px";
color: "lightgrey";
}
.no-break-inside {
// apply this class to every component that shouldn't be cut off between to pages of your PDF
break-inside: "avoid";
}
.break-before {
// apply this class to every component that should always display on next page
break-before: "always";
}
}
<MyComponent isPrintable=true className="no-break-inside" />
Estos pocos consejos de CSS deberían ayudarlo a mejorar mucho la representación de su página web.
Ahora, su página está lista para imprimir. Lo sabes cuando pasas los isPrintable
accesorios a tu página, haces clic con el botón derecho + imprimir en tu navegador y te sientes bastante cómodo con lo que ves. Aquí viene la parte de la impresión. Ahora tiene una versión imprimible de su página web, pero los usuarios no tienen idea de ella, e incluso si presionan ctrl + P en el sitio web, verán la versión "dinámica" de la página web. Para permitirles generar la versión PDF y automatizar la generación de la última, probablemente desee agregar un botón que genere directamente el lado del servidor PDF e incluso agregar algo de personalización. Para esto se utiliza, entre otras cosas, Titiritero.
Titiritero es una forma común y natural de controlar Chrome. Proporciona acceso completo a las funciones del navegador y, lo que es más importante, puede ejecutar Chrome en modo totalmente autónomo en un servidor remoto [...]
—Dima Bekerman, https://www.imperva.com/blog/headless-chrome-devops-love-it-so-do-hackers-heres-why/
![]() |
---|
Esquema de cómo funciona Titiritero del lado del servidor |
La generación de la aplicación React se realiza mediante un navegador web. Necesitamos el entorno mínimo capaz de ejecutar javascript para representar un DOM. Titiritero lo hará lanzando un cromo sin cabeza. A partir de ahora, y dado que la generación se realiza en el servidor, el navegador web no necesita tener una interfaz gráfica de usuario (GUI). Chromium genera la versión imprimible: la misma página que el usuario ve en su navegador web pero con los isPrintable
accesorios activados. Entonces Puppeteer ejecutará la pdf
función en la página con algunas opciones personalizadas que activarán la impresión de la página.
Simplemente agregue el botón con la URL que llama al servicio de impresión:
<Button onClick={window.open(downloadUrl, "_blank")}>Download as PDF</Button>
En downloadUrl
realidad, es una solicitud GET en su servidor que ejecutará Titiritero en el servidor y devolverá contenido con tipo de contenidoapplication/pdf
Entonces, ¿cómo es este código de Titiritero?
Para poder descargar el PDF, solo necesita unas pocas líneas de código.
El código mínimo se vería así:
const puppeteer = require("puppeteer");
(async () => {
const browser = await puppeteer.launch(); // launch a browser (chromium by default but you can chose another one)
const page = await browser.newPage(); // open a page in the browser
await page.goto("https://printable-version-of-my-wbe-page.com", {
waitUntil: "networkidle2",
}); // visit the printable version of your page
await page.pdf({ format: "a4", path: "./my_file.pdf" }); // generate the PDF 🎉
await browser.close(); // don't forget to close the browser. Otherwise, it may cause performances issues or the server may even crash..
})();
Estos son los pasos comunes que necesitará para generar el PDF. Dependiendo de su back-end, probablemente no quiera descargar el PDF en el servidor sino representarlo en un objeto de respuesta, para enviarlo de regreso al cliente (el navegador web del usuario). Luego debe adaptar el page.pdf()
método const buffer = await page.pdf({ format: 'a4'});
y devolver este búfer en la _blank
página que el usuario abrió en su navegador, esperando una respuesta.
Por supuesto, puede adaptar las opciones que naturalmente tiene en su navegador, como el tamaño del papel, la escala, los márgenes, etc. con la ayuda de la documentación oficial: https://github.com/puppeteer/puppeteer/blob/v10 .4.0/docs/api.md#pagepdfoptions .
Una buena opción que recomiendo, principalmente porque la predeterminada que proporciona Google Chrome es realmente fea, es la plantilla de encabezado o pie de página. Simplemente lea una plantilla de archivo HTML y pásela por los datos que desea mostrar, como la fecha actual, el número de página para cada página o incluso una imagen/logotipo:
const footerBase = fs.readFileSync("./footer.html", "utf8");
customFooter = footerBase
.replace("{{date}}", new Date())
.replace("{{image_data}}", imageData);
await page.pdf({ format: "a4", footerTemplate: customFooter });
utilizando una plantilla html
<style>
#logo {
height: 40px;
content: url("data:image/png;base64,{{image_data}}");
}
</style>
<div id="body">
<div id="page-number-paragraph">
<span id="date">{{date}}</span>
<span>Page</span>
<span class="pageNumber"/></span>
<span>/</span>
<span class="totalPages"></span>
</div>
<div id="brand-container">
<span id="logo"></span>
</div>
</div>
Ahora ha proporcionado a su PDF un pie de página totalmente personalizado.
Hay muchas otras opciones con respecto a la generación de PDF, pero también para los pasos previos de iniciar el navegador, abrir una nueva página, ir a la URL, que le permitirán personalizar completamente su generación de PDF en el servidor.
Finalmente, al adaptar su código React/CSS y usar Puppeteer, puede proporcionar fácilmente un PDF totalmente personalizado de su página. Además, Puppeteer está haciendo todas las cosas del lado del servidor. ¡Lo que hace que esta función sea completamente transparente, bastante rápida para el usuario final y con el mismo resultado para todos los usuarios en cualquier navegador! Puppeteer es realmente poderoso y tiene muchas opciones que hacen que la generación de PDF sea bastante fácil para los desarrolladores, y con un renderizado mucho más personalizado y hermoso que el predeterminado en los navegadores de los usuarios.
Fuente: https://blog.theodo.com/2021/10/pdf-generation-react-puppeteer/
1659016800
Gần đây tôi đã phải cung cấp một chức năng mới cho dự án của mình: chức năng "tải xuống dưới dạng PDF". Điều đầu tiên tôi tự hỏi là tại sao chúng tôi nên cung cấp chức năng này? Không phải nó đã tồn tại nguyên bản với hầu hết các trình duyệt web với tùy chọn nhấp chuột phải / print / "save as pdf"? Tôi đã thử trên trang web của mình và kết quả thực sự đáng thất vọng:
Ví dụ trên trang ngẫu nhiên Boursorama , giống như sau:
kết xuất:
Đó là khi tôi nói "ok, tính năng này có thể đáng giá", nhưng tôi nên làm như thế nào? Có nhiều thư viện mã nguồn mở có thể tạo PDF. Nhưng sự lựa chọn của tôi tự nhiên đến với thư viện nổi tiếng do google phát triển: Puppeteer. Theo tôi, đó là cách dễ nhất để giải quyết việc tạo ra các Ứng dụng Trang đơn PDF. Có thể không phải như vậy nếu bạn không xử lý các gói javascript nhưng với HTML / CSS thuần túy. Thật vậy, có những giải pháp dễ dàng hơn cho trường hợp sử dụng này như wkhtmltopdf, html-pdf-node hoặc jspdf chẳng hạn.
Trong bài viết này, tôi muốn cung cấp cho bạn một số mẹo để tạo các tệp PDF đẹp của các SPA với Puppeteer. Đầu tiên, tôi sẽ giải thích cho bạn cách bạn có thể tạo phiên bản có thể in được trên trang của mình bằng React và Puppeteer. Sau đó, tôi sẽ chỉ cho bạn cách sử dụng Puppeteer để tạo trang có thể in mới của bạn.
Đối với phần này, bạn thực sự không cần phải thiết lập Puppeteer hoặc bất kỳ dịch vụ máy in nào khác. Bạn có thể thực hiện các thay đổi đối với mã của mình như bình thường, sau đó nhấn ctrl + P trên trang của bạn để xem nó trông như thế nào:
Tuy nhiên, vòng lặp phản hồi không nhanh như bình thường.
Để điều chỉnh mã của bạn để in, bạn phải bỏ qua 2 điểm khác biệt chính giữa trang web và PDF:
Tạo "phiên bản có thể in" của SPA của bạn bằng React. Để tạo phiên bản có thể in của trang của chúng tôi, bạn sẽ phải thêm / xóa / sửa đổi các thành phần khác nhau tạo nên trang.
Về cơ bản, bạn có 2 giải pháp cho phần này:
Nếu bạn chọn giải pháp thứ hai (ít tốn kém hơn), bạn sẽ phải điều chỉnh các thành phần hiện có của mình. Ví dụ: nếu bạn có một bảng với 3 tab, có thể bạn sẽ muốn hiển thị nội dung của tất cả các tab. Một cái gì đó như hiển thị các tab lần lượt có thể thực hiện thủ thuật:
Chỉ động:
<Table selectedTabIndex="tab1" />
Động và Tĩnh:
const tabNames = ['tab1', 'tab2', 'tab3']
(isPrintable ?
tabNames.map(tabName => <Table key={tabName} selectedTab={tabName} /> :
<Table selectedTabIndex='tab1' />
);
Trong trường hợp này, các isPrintable
đạo cụ sẽ quyết định hiển thị 3 tab hay chỉ hiển thị tab đầu tiên. Bạn có thể chuyển đạo cụ này cho mọi thành phần động trên trang của mình, cần được điều chỉnh để in.
Như bạn có thể thấy với ví dụ Boursorama, các thành phần của bạn có thể bị cắt giữa 2 trang khi cố gắng in trang của bạn. Nó xảy ra vì trình duyệt web của bạn không biết phải ngắt trang ở đâu nếu bạn không nói cho anh ta biết. Đây là nơi thuộc tính break-inside
CSS bước vào. Rõ ràng là bạn không muốn tập hợp các tab trước đó của mình bị cắt ở giữa. Không có đồ thị của bạn hoặc hầu như bất kỳ thành phần nào trên trang của bạn. Sau đó, bạn sẽ phải điều chỉnh mã trước đó để thêm thuộc tính CSS này. Nó sẽ hoạt động với inline-css nhưng bạn có thể không muốn thêm mọi thứ style={{ breakInside: 'avoid' }}
vào mọi nơi trong tệp jsx / tsx của mình.
Bạn muốn sử dụng bảng định kiểu. Và thay vì thêm thuộc tính này trên mọi lớp CSS đã tồn tại, bạn sẽ muốn sử dụng media @print
tùy chọn. Điều này sẽ cho phép bạn tùy chỉnh trang web của mình để chỉ in! Ví dụ: bạn có thể muốn văn bản của mình lớn hơn một chút hoặc có màu xám mịn trên phiên bản có thể in được, vì bất kỳ lý do thẩm mỹ hoặc tiện lợi nào.
Chúng tôi sẽ chỉ thêm cái này vào @media object
tệp css của bạn:
media @print {
body: {
font-size: "16px";
color: "lightgrey";
}
.no-break-inside {
// apply this class to every component that shouldn't be cut off between to pages of your PDF
break-inside: "avoid";
}
.break-before {
// apply this class to every component that should always display on next page
break-before: "always";
}
}
<MyComponent isPrintable=true className="no-break-inside" />
Một vài mẹo CSS này sẽ giúp bạn cải thiện rất nhiều việc hiển thị trang web của mình.
Bây giờ, trang của bạn đã sẵn sàng để in. Bạn biết điều đó khi chuyển các isPrintable
đạo cụ đến trang của mình, nhấp chuột phải + in trên trình duyệt của mình và bạn khá thoải mái với những gì mình nhìn thấy. Đây là phần in ấn. Bây giờ bạn có phiên bản có thể in của trang web của mình, nhưng người dùng không biết về nó và ngay cả khi nhấn ctrl + P trên trang web, họ sẽ thấy phiên bản "động" của trang web. Để cho phép họ tạo phiên bản PDF và tự động tạo phiên bản mới nhất, bạn có thể muốn thêm một nút sẽ tạo trực tiếp phía máy chủ PDF và thậm chí thêm một số tùy chỉnh. Đây là thứ, trong số những thứ khác, Puppeteer được sử dụng để làm.
Puppeteer là một cách phổ biến và tự nhiên để điều khiển Chrome. Nó cung cấp quyền truy cập đầy đủ vào các tính năng của trình duyệt và quan trọng nhất là có thể chạy Chrome ở chế độ hoàn toàn không sử dụng đầu trên máy chủ từ xa […]
—Dima Bekerman, https://www.imperva.com/blog/headless-chrome-devops-love-it-so-do-hackers-heres-why/
![]() |
---|
Lược đồ về cách Puppeteer hoạt động phía máy chủ |
Việc tạo ra ứng dụng React được thực hiện bởi trình duyệt web. Chúng tôi cần môi trường tối thiểu có thể thực thi javascript để hiển thị DOM. Puppeteer sẽ làm điều đó bằng cách phóng một crôm không đầu. Kể từ bây giờ, và vì quá trình tạo được thực hiện trên máy chủ, nên trình duyệt web không cần phải có giao diện người dùng đồ họa (GUI). Chromium với tạo phiên bản có thể in: cùng một trang mà người dùng nhìn thấy trên trình duyệt web của mình nhưng đã isPrintable
kích hoạt đạo cụ. Sau đó Puppeteer sẽ thực thi pdf
chức năng trên trang với một số tùy chọn tùy chỉnh sẽ kích hoạt việc in trang.
Chỉ cần thêm nút có URL gọi dịch vụ máy in:
<Button onClick={window.open(downloadUrl, "_blank")}>Download as PDF</Button>
Đây downloadUrl
thực sự là một yêu cầu GET trên máy chủ của bạn sẽ thực thi Puppeteer trên máy chủ và trả về nội dung có kiểu nội dungapplication/pdf
Vậy mã Puppeteer này trông như thế nào?
Để có thể thực sự tải xuống PDF, bạn chỉ cần một vài dòng mã.
Sau đó, mã tối thiểu sẽ trông giống như sau:
const puppeteer = require("puppeteer");
(async () => {
const browser = await puppeteer.launch(); // launch a browser (chromium by default but you can chose another one)
const page = await browser.newPage(); // open a page in the browser
await page.goto("https://printable-version-of-my-wbe-page.com", {
waitUntil: "networkidle2",
}); // visit the printable version of your page
await page.pdf({ format: "a4", path: "./my_file.pdf" }); // generate the PDF 🎉
await browser.close(); // don't forget to close the browser. Otherwise, it may cause performances issues or the server may even crash..
})();
Đây là các bước phổ biến bạn sẽ cần để tạo tệp PDF. Tùy thuộc vào chương trình phụ trợ của bạn, có thể bạn không muốn tải xuống tệp PDF trên máy chủ mà hiển thị nó trên một đối tượng phản hồi, để gửi nó trở lại máy khách (trình duyệt web của người dùng). Sau đó, bạn nên điều chỉnh page.pdf()
phương pháp const buffer = await page.pdf({ format: 'a4'});
và trả lại vùng đệm này trên _blank
trang mà người dùng đã mở trên trình duyệt của mình, chờ phản hồi.
Tất nhiên, bạn có thể điều chỉnh các tùy chọn tự nhiên có trên trình duyệt của mình, như khổ giấy, tỷ lệ, lề, v.v. với sự trợ giúp của tài liệu chính thức: https://github.com/puppeteer/puppeteer/blob/v10 .4.0 / docs / api.md # pagepdfoptions .
Một tùy chọn thú vị mà tôi đề xuất, chủ yếu là vì tùy chọn mặc định do Google Chrome cung cấp thực sự xấu, là mẫu đầu trang hoặc chân trang. Chỉ cần đọc mẫu tệp HTML và chuyển nó qua dữ liệu bạn muốn hiển thị, chẳng hạn như ngày hiện tại, số trang cho mỗi trang hoặc thậm chí là hình ảnh / biểu trưng:
const footerBase = fs.readFileSync("./footer.html", "utf8");
customFooter = footerBase
.replace("{{date}}", new Date())
.replace("{{image_data}}", imageData);
await page.pdf({ format: "a4", footerTemplate: customFooter });
sử dụng mẫu html
<style>
#logo {
height: 40px;
content: url("data:image/png;base64,{{image_data}}");
}
</style>
<div id="body">
<div id="page-number-paragraph">
<span id="date">{{date}}</span>
<span>Page</span>
<span class="pageNumber"/></span>
<span>/</span>
<span class="totalPages"></span>
</div>
<div id="brand-container">
<span id="logo"></span>
</div>
</div>
Bây giờ bạn đã cung cấp cho tệp PDF của mình một chân trang được tùy chỉnh hoàn toàn.
Có rất nhiều tùy chọn khác liên quan đến việc tạo PDF, mà còn đối với các bước khởi chạy trình duyệt trước đó, mở trang mới, truy cập URL, điều này sẽ cho phép bạn tùy chỉnh hoàn toàn việc tạo PDF của mình trên máy chủ.
Cuối cùng, bằng cách điều chỉnh mã React / CSS của bạn và sử dụng Puppeteer, bạn có thể dễ dàng cung cấp một tệp PDF hoàn toàn tùy chỉnh cho trang của mình. Hơn nữa, Puppeteer đang làm tất cả những thứ ở phía máy chủ. Điều này làm cho tính năng này hoàn toàn minh bạch, khá nhanh cho người dùng cuối và mang lại kết quả tương tự cho mọi người dùng trên bất kỳ trình duyệt nào! Puppeteer thực sự mạnh mẽ và có nhiều tùy chọn giúp tạo PDF khá dễ dàng cho các nhà phát triển, đồng thời kết xuất tùy chỉnh và đẹp hơn nhiều so với mặc định trên trình duyệt của người dùng.
Nguồn: https://blog.theodo.com/2021/10/pdf-generation-react-puppeteer/
1650427472
Cross-browser CSS3 animation library
If you're a web hipster, and you're already using Twitter's Bootstrap like a whore, you're gonna love this.
animate.less
, originally created by Dan Eden, is a bunch of cool, fun, and cross-browser animations converted into LESS for you to use in your Bootstrap projects. Great for emphasis, home pages, sliders, and general just-add-water-awesomeness.
animate.css
is 64kb and animate.min.css
is 48kb.
You can use bower, npm, or download a zip.
bower --save install animate.less
npm --save install animate.less
To use animate.less in your Bootstrap project, simply add the line below into bootstrap.less
:
@import "<PATH_TO_SOURCE>/animate.less";
Add the class animated
to an element, along with any of the animation names. That's it! You've got a CSS animated element. Super!
For example:
<h1 id="logo" class="animated fadeIn">...</h1>
Since we're using LESS, we can utilize our animation library by adding .animated
and any of the many animation names as classes.
For example:
h1#logo {
float: left;
font-family: 'Cubano', sans-serif;
font-weight: normal;
font-size: 3.5em;
text-transform: uppercase;
padding: 0;
margin: 0;
.animated; // Initiate animation library
.bounce; // Initiate bounce effect
}
Note: if you're having issues, try re-compiling bootstrap.less
for changes to take effect.
You can add more functionality to your animations with jQuery such as below:
$("#logo").addClass('animated bounceOutLeft');
We can also bind these classes with events or triggers like below:
$(document).ready(function(){
$("#logo").click(function() {
$("this").addClass("animated bounceOutLeft");
});
});
You can
@animationDurationTime
@animationDelayTime
@animationFillMode
orIf you do edit an animation effect, make sure you change them cross-browser.
#logo {
-vendor-animation-duration: 3s; // Change to Webkit, Mozilla, Opera, etc.
-vendor-animation-delay: 2s;
-vendor-animation-iteration-count: infinite;
animation-duration 3s; // Default
animation-delay: 2s; // Default
animation-iteration-count: infinite; // Default
}
Note: be sure to replace "vendor" in the CSS with the applicable vendor prefixes (webkit, moz, ms, o).
Note: Safari in Mountain Lion (OS X 10.8) has a display glitch with the Flippers. They may not appear at all until the animation is completed, or the page may have other artifacting. One fix is to add overflow: hidden to the parent div.
Head over to http://daneden.me/animate/build/ to select which animations you need. This will download a .css file, so just rename it to .less and use as described above.
You can also pick & choose which LESS files in your own LESS, just set the variables & import stuff (see animate.less for example):
@animationDurationTime: 0.5s;
@animationDelayTime: 0s;
@animationFillMode: both;
@animationLessLocation: '../node_modules/animate.less/source';
@import "@{animationLessLocation}/animated.less";
@import "@{animationLessLocation}/bounce.less";
@import "@{animationLessLocation}/bounceIn.less";
@import "@{animationLessLocation}/bounceInDown.less";
@import "@{animationLessLocation}/bounceInLeft.less";
@import "@{animationLessLocation}/bounceInRight.less";
@import "@{animationLessLocation}/bounceInUp.less";
@import "@{animationLessLocation}/bounceOut.less";
@import "@{animationLessLocation}/bounceOutDown.less";
@import "@{animationLessLocation}/bounceOutLeft.less";
@import "@{animationLessLocation}/bounceOutRight.less";
@import "@{animationLessLocation}/bounceOutUp.less";
View the animation library in action over at http://daneden.me/animate/.
Since we're using CSS3 here, we're limited to only modern browsers like Chrome, Safari, Mozilla, and Opera.
Animate.css is licensed under the ☺ license. (http://licence.visualidiot.com/)
I'll shortly be compiling an animation-library.less
which will allow you to pick and choose which effects you need in your project.
Down the line, IE8/9 support hopefully.
You can learn more about animate.css over at http://daneden.me/animate and Twitter Bootstrap over at http://getbootstrap.com/
flash bounce shake tada swing wobble wiggle pulse
flip flipInX flipOutX flipInY flipOutY
fadeIn fadeInUp fadeInDown fadeInLeft fadeInRight fadeInUpBig fadeInDownBig fadeInLeftBig fadeInRightBig
fadeOut fadeOutUp fadeOutDown fadeOutLeft fadeOutRight fadeOutUpBig fadeOutDownBig fadeOutLeftBig fadeOutRightBig
bounceIn bounceInDown bounceInUp bounceInLeft bounceInRight
bounceOut bounceOutDown bounceOutUp bounceOutLeft bounceOutRight
rotateIn rotateInDownLeft rotateInDownRight rotateInUpLeft rotateInUpRight
rotateOut rotateOutDownLeft rotateOutDownRight rotateOutUpLeft rotateOutUpRight
lightSpeedIn lightSpeedOut
hinge rollIn rollOut
npm install
to install the dev tools.npm run build
to build the CSS files. Check out package.json
to see how to add these tools to your own LESS-based project.Author: machito
Source Code: https://github.com/machito/animate.less
License:
1641850620
Hello guys!
Today we brought to you a new video about "Logo Design Trends 2022"
1640624166
Make your Design Project looks Fancier with this Unique Hynole Font. Take a Look at Here to Know More https://bit.ly/3F9gtKm
1637944604
Avail High-resolution and Easy Customizable Logo from Draftik to Enhance your Brand Identity. Find Out Here https://bit.ly/3nxcgbZ
1636729179
Get an exclusive Ninja Esport Logo for your gaming team, YouTube game, T-shirt design, online games, E-sports and mobile games from Draftik. Check out here https://bit.ly/2Z0Tod4
1636553755
Unlock your products now! Exclusive deals and great templates offer to explore more simply click https://www.draftik.net/super-deals/super-templates-deal