我对React还是很陌生,我正在开发一个应用程序,它将获取网页的实际屏幕截图,并且该应用程序可以在所截取的屏幕截图之上绘制并添加涂鸦。最初,我使用html2canvas和domToImage拍摄客户端屏幕快照,但它无法完全呈现网页中显示的图像。
Reddit用户/ pamblam0建议我调查Google的Puppeteer。它的工作方式是启动无头铬浏览器,该浏览器转到我在本地主机上的react应用,然后轻松获取整个页面的屏幕截图。但是我的问题是,木偶戏在React应用程序中不能很好地发挥作用。它给了我一个ws错误,正如在Google搜索中所述,可以通过简单地安装ws来解决(这是行不通的)。
现在,我的操纵my脚本可以完成我的react应用程序。据我了解,它不适用于客户端应用程序(我可能错了)。我想发生的是,每当我单击react应用程序中的按钮时,puppeteer应该执行并返回base64字符串,然后将其传递到我的react应用程序中的组件。
到目前为止,这是我所做的。
puppeteerApp.js
const puppeteer = require('puppeteer'); const takeScreenshot = async () => { puppeteer.launch().then(async browser => { const page = await browser.newPage(); const options = { path: 'saved_images/webshot.png', encoding: 'base64' } await page.goto('http://localhost:3000/', { waitUntil: 'networkidle2' }); const elem = await page.$('iframe').then(async (iframe) => { return await iframe.screenshot(options) }); await browser.close() }); } takeScreenshot();
来自React应用的代码 App.js
import React, { Component } from 'react'; import ScreenshotsContainer from './containers/ScreenshotsContainer/ScreenshotsContainer' import ImageContainer from './containers/ImageContainer/ImageContainer'; import html2canvas from 'html2canvas'; import domtoimage from 'dom-to-image'; import Button from './components/UI/Button/Button' import classes from './App.module.css'; import { CSSTransition } from 'react-transition-group' import { ToastContainer, toast } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; class App extends Component { constructor(props) { super(props); this.state = { imgURIArray: [], img: null, showImageContainer: false, showScreenshotContainer: false, selectedImageURI: null, showSaveAnimation: false, showNotify: false } } storeImageToArrayHandler = (imgURI) => { if (imgURI !== "") { this.setState({ imgURIArray: [...this.state.imgURIArray, imgURI] }, () => { this.setState({ showImageContainer: !this.state.showImageContainer }) }) } } getScreenshotHandler = () => { //use puppeteer here!!! } getSelectedImageFromContainerHandler(selectedImageURI) { this.setState({ selectedImageURI: selectedImageURI, showImageContainer: !this.state.showImageContainer }) } showImageContainerHandler(showImageContainer) { this.setState({ showImageContainer: showImageContainer }) } showScreenshotContainerHandler = () => { this.setState({ showScreenshotContainer: !this.state.showScreenshotContainer }) } notify = (submitSuccessful, msg) => { let message = msg ? msg : "" submitSuccessful ? toast.success(message, { autoClose: 3000, position: toast.POSITION.TOP_CENTER }) : toast.error(message, { position: toast.POSITION.TOP_CENTER }); } render() { let buttonOps = ( <CSSTransition in={!this.state.showScreenshotContainer} appear={true} timeout={300} classNames="fade" > <div className={classes.optionButtons}> <Button icon={"fas fa-camera"} type={"button-success"} gridClass={""} buttonName={""} style={{ width: "100%", height: "70px" }} onClick={() => this.getScreenshotHandler} /> <Button icon={"fas fa-images"} type={"button-primary "} gridClass={""} buttonName={""} style={{ width: "100%", height: "70px" }} onClick={() => this.showScreenshotContainerHandler} /> </div> </CSSTransition> ) return ( <div> { this.state.showImageContainer ? <div> <ImageContainer img={this.state.img} showImageContainer={showImageContainer => this.showImageContainerHandler(showImageContainer)} storeImageToArrayHandler={imgURI => this.storeImageToArrayHandler(imgURI)} notify={(submitSuccessful, msg) => this.notify(submitSuccessful, msg)} /> </div> : null } <CSSTransition in={this.state.showScreenshotContainer} appear={true} timeout={300} classNames="slide" unmountOnExit onExited={() => { this.setState({ showScreenshotContainer: false }) }} > <ScreenshotsContainer imgURIArray={this.state.imgURIArray} getSelectedImageFromContainerHandler={imgURI => this.getSelectedImageFromContainerHandler(imgURI)} showScreenshotContainerHandler={() => this.showScreenshotContainerHandler} notify={(submitSuccessful, msg) => this.notify(submitSuccessful, msg)} /> </CSSTransition> {this.state.showImageContainer ? null : buttonOps} {/* <button onClick={this.notify}>Notify !</button> */} <ToastContainer /> </div > ); } } export default App;
任何帮助,将不胜感激。谢谢!
您的React.js应用程序在客户端(在浏览器中)运行。由于无法在浏览器中启动完整的浏览器,因此Puppeteer无法在该环境中运行。
您需要的是一台为您完成任务的服务器。您可以提供一个HTTP端点(选项1)或公开您的伪造Websocket(选项2):
对于此选项,您可以设置服务器来处理传入的请求并为您运行任务(制作屏幕截图):
server.js
const puppeteer = require('puppeteer'); const express = require('express'); const app = express(); app.get('/screenshot', async (req, res) => { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto(req.query.url); // URL is given by the "user" (your client-side application) const screenshotBuffer = await page.screenshot(); // Respond with the image res.writeHead(200, { 'Content-Type': 'image/png', 'Content-Length': screenshotBuffer.length }); res.end(screenshotBuffer); await browser.close(); }) app.listen(4000);
使用启动应用程序,node server.js您现在可以将URL传递到服务器,并从服务器获取屏幕截图:http://localhost:4000/screenshot?url=https://example.com/
node server.js
http://localhost:4000/screenshot?url=https://example.com/
来自服务器的响应然后可以用作应用程序中图像元素的源。
您还可以通过公开Websocket从客户端控制浏览器(在服务器上运行)。
为此,您需要像这样公开服务器的Websocket:
const puppeteer = require('puppeteer'); (async () => { const browser = await puppeteer.launch(); const browserWSEndpoint = browser.wsEndpoint(); browser.disconnect(); // Disconnect from the browser, but don't close it console.log(browserWSEndpoint); // Communicate the Websocket URL to the client-side // example output: ws://127.0.0.1:55620/devtools/browser/e62ec4c8-1f05-42a1-86ce-7b8dd3403f91 })();
现在,您可以通过客户端的操纵up捆绑包来控制客户端上运行的浏览器(在服务器上运行)。在这种情况下,您现在可以通过puppeteer.connect连接到浏览器并以这种方式制作屏幕截图。
我强烈建议您使用选项1,因为在选项2中,您正在将正在运行的浏览器完全暴露给客户端。即使使用选项1,您仍然需要处理用户输入验证,超时,导航错误等。