简体   繁体   English

ReactJS - 打印 DOM 中的特定元素

[英]ReactJS - Print specific elements in the DOM

I am using ReactJS on an App and currently need to be able to print some elements from the page on user's request (click on a button).我在应用程序上使用ReactJS ,目前需要能够根据用户请求(单击按钮)从页面打印一些元素

I chose to use the CSS media-query type print ( @media print ) to be able to check if an element should be printed, based on a selector that could be from a class or attribute on an Element.我选择使用CSS 媒体查询类型打印( @media print ) 来检查是否应打印元素,基于可能来自 class 或元素属性的选择器。 The strategy would be to hide everything but those "printable" elements with a stylesheet looking like:策略是隐藏除那些“可打印”元素之外的所有元素,样式表如下所示:

@media print {
    *:not([data-print]) {
        display: none;
    }
}

However, for this to work I need to also add the chosen print selector (here the attribute data-print ) on every parent element each printable element has.但是,为此,我还需要在每个可打印元素具有的每个父元素上添加所选的打印选择器(此处为属性data-print )。

To do that here's what I've tried so far:为此,这是我迄今为止尝试过的:

export default function PrintButton() {
    useEffect(() => {
        const handleBeforePrint = () => {
            printNodeSelectors.forEach((selector) => {
                const printableElement = document.querySelector(selector);
                if (printableElement != null) {
                    let element = printableElement;
                    while (element.parentElement) {
                        element.setAttribute("data-print", "");
                        element = element.parentElement;
                    }
                    element.setAttribute("data-print", "");
                }
            });
        };
        const handleAfterPrint = () => {
            printNodeSelectors.forEach((selector) => {
                const printableElement = document.querySelector(selector);
                if (printableElement != null) {
                    let element = printableElement;
                    while (element.parentElement) {
                        element.removeAttribute("data-print");
                        element = element.parentElement;
                    }
                    element.removeAttribute("data-print");
                }
            });
        };
        window.addEventListener("beforeprint", handleBeforePrint);
        window.addEventListener("afterprint", handleAfterPrint);
        return () => {
            window.removeEventListener("beforeprint", handleBeforePrint);
            window.removeEventListener("afterprint", handleAfterPrint);
        };
    }, []);

    return <button onClick={() => window.print()}>Print</button>;
}

With printNodeSelectors being a const Array of string selectors. printNodeSelectors是字符串选择器的常量数组。

Unfortunately it seems that React ditch out all my dirty DOM modification right after I do them不幸的是,React 似乎在我完成修改后立即抛弃了我所有的脏 DOM 修改

I'd like to find a way to achieve this without having to manually put everywhere in the app who should be printable, while working on a React App, would someone knows how to do that?我想找到一种方法来实现这一点,而不必在应用程序中手动放置应该可打印的任何地方,同时在 React 应用程序上工作,有人知道该怎么做吗?

Just CSS should be enough to hide all Elements which do not have the data-print attribute AND which do not have such Element in their descendants .只需 CSS 就足以隐藏所有没有data-print属性并且在其后代中没有此类元素的元素。

Use the :has CSS pseudo-class (in combination with :not one) to express that 2nd condition (selector on descendants):使用:has CSS 伪类(结合:not one)来表达第二个条件(后代选择器):

@media print {
  *:not([data-print]):not(:has([data-print])) {
    display: none;
  }
}

Caution: ancestors of Elements with data-print attribute would not match, hence their text nodes (not wrapped by a tag) would not be hidden when printing:注意:具有data-print属性的元素的祖先不会匹配,因此它们的文本节点(未被标签包裹)在打印时不会被隐藏:

<div>
  <span>should not print</span>
  <span data-print>but this should</span>
  Caution: text node without tag may be printed...
</div>

Demo: https://jsfiddle.net/6x34ad50/1/ (you can launch the print preview browser feature to see the effect, or rely on the coloring) Demo: https://jsfiddle.net/6x34ad50/1/ (可以打开打印预览浏览器功能看效果,或者靠上色)

Similar but just coloring to directly see the effect:类似但只是着色直接看到效果:

 *:not([data-print]):not(:has([data-print])) { color: red; }
 <div> <span>should not print (be colored in red)</span> <span data-print>but this should</span> Caution: text node without tag may be printed... </div>

After some thoughts, tries and errors it appears that even though I managed to put the attribute selector on the parents I completely missed the children of the elements I wanted to print!经过一些思考、尝试和错误后,即使我设法将属性选择器放在父元素上,我也完全错过了我想要打印的元素的子元素! (React wasn't at all removing the attributes from a mysterious render cycle in the end) (React 最终并没有从神秘的渲染周期中移除属性)

Here's a now functioning Component:这是一个正在运行的组件:

export default function PrintButton() {
    
    useEffect(() => {
        const handleBeforePrint = () => {
            printNodeSelectors.forEach((selector) => {
                const printableElement = document.querySelector(selector);
                if (printableElement != null) {
                    const elements: Element[] = [];
                    // we need to give all parents and children a data-print attribute for them to be displayed on print
                    const addParents = (element: Element) => {
                        if (element.parentElement) {
                            elements.push(element.parentElement);
                            addParents(element.parentElement);
                        }
                    };
                    addParents(printableElement);
                    const addChildrens = (element: Element) => {
                        elements.push(element);
                        Array.from(element.children).forEach(addChildrens);
                    };
                    addChildrens(printableElement);
                    elements.forEach((element) => element.setAttribute("data-print", ""));
                }
            });
        };
        const handleAfterPrint = () => {
            document.querySelectorAll("[data-print]").forEach((element) => element.removeAttribute("data-print"));
        };
        window.addEventListener("beforeprint", handleBeforePrint);
        window.addEventListener("afterprint", handleAfterPrint);
        return () => {
            window.removeEventListener("beforeprint", handleBeforePrint);
            window.removeEventListener("afterprint", handleAfterPrint);
        };
    }, []);

    return <button onClick={() => window.print()}>Print</button>;
}

I usually don't like messing with the DOM while using React but here it allows me to keep everything in the component without having to modify anything else around (though I'd agree that those printNodeSelectors need to be chosen from outside and aren't dynamic at the moment)我通常不喜欢在使用 React 时弄乱 DOM,但在这里它允许我将所有内容保留在组件中而无需修改周围的任何其他内容(尽管我同意这些 printNodeSelectors 需要从外部选择而不是目前动态)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM