MatthewHerbst / react-to-print

Print React components in the browser. Supports Chrome, Safari, Firefox and EDGE
MIT License
2.14k stars 221 forks source link

Content is not displayed until the end of the last page #659

Closed titoworlddev closed 1 year ago

titoworlddev commented 1 year ago

I have the print component that is like a gym workout and I want an image to be seen in the background, but this image is not displayed on all the complete pages. No matter how much I try to set height: 100% anywhere, it doesn't work and I would like the content to go as far as it needs to go, but if when it is final there is still half a page of the pdf, I want the body to extend until the end of that page so that the image that I put in the background can be seen in full throughout the pdf, since right now it is cut off, the photo is visible and in the middle of the page everything is white until the end.

You can see the example in these images:

image image image

As I said, in the end it cuts off and looks white and I can't stretch the bodysuit so that it fills everything with the image. Any idea how to fix this?

I put my component code and the CSS in case it helps me.

WorkoutToPrins.jsx

import { useContext, useEffect, useState } from 'react';
import { getExercises } from '../../services/getExercises';
import './WorkoutToPrint.css';
import { LocalStorageContext } from '../../contexts/LocalStorage/LocalStorageContext';
import { weekDays } from '../../utils/variables';
import BarbellIcon from '../Icons/BarbellIcon';
import GymPersonIcon from '../Icons/GymPersonIcon';

export default function WorkoutToPrint({ useRef }) {
  const { pdfImageURL, userClients, currentClientIndex, currentClient } =
    useContext(LocalStorageContext);

  const [currentWorkout, setCurrentWorkout] = useState(
    userClients[currentClientIndex].workouts[
      userClients[currentClientIndex].currentWorkoutIndex
    ]
  );

  useEffect(() => {
    setCurrentWorkout(
      userClients[currentClientIndex].workouts[
        userClients[currentClientIndex].currentWorkoutIndex
      ] || {}
    );
  }, [userClients]);

  return (
    <div
      ref={useRef}
      className="workout-to-print"
      // @ts-ignore
      style={{ '--background-image': `url(${pdfImageURL})` }}
    >
      <div className="pdf-content">
        <div className="pdf-title">
          <div className="title-1">
            <BarbellIcon />
            <div className="hr"></div>
          </div>

          <h1>{currentClient.clientName}</h1>

          <div className="title-2">
            <div className="hr"></div>
            <GymPersonIcon />
          </div>
        </div>

        {currentWorkout.workoutDays
          ? currentWorkout.workoutDays.map((day, index) => (
              <div className="workout-to-print-day" key={day.dayName + index}>
                <h2 className="workout-to-print-day-title">{`${weekDays[index]} - ${day.dayName}`}</h2>

                <div className="workout-to-print-day-exercises">
                  {day.dayExercises.map((exercise, index) => {
                    const exer = getExercises().find(
                      exer => exer.id === exercise.exerciseId
                    );

                    return (
                      <div
                        key={exercise.exerciseId + index}
                        className="workout-to-print-day-exercise"
                      >
                        <img alt="img" src={exer?.gifUrl} />

                        <div className="exercise-texts">
                          <h3>
                            {exer?.name
                              // @ts-ignore
                              .capitalize()}
                          </h3>
                          <p>
                            Target:{' '}
                            {exer?.target
                              // @ts-ignore
                              .capitalize()}
                          </p>
                          <p>Sets: {exercise.sets}</p>
                          <p>Reps: {exercise.reps}</p>
                        </div>
                      </div>
                    );
                  })}
                </div>
              </div>
            ))
          : null}
      </div>
    </div>
  );
}

WorkoutToPrint.css

@media print {
  @page {
    size: portrait;
  }

  html,
  body {
    margin: 0 !important;
    padding: 0 !important;
    background-color: white;
  }
}

.workout-to-print {
  color: #000;
  flex-direction: column;
  gap: 1rem;
  font-size: 9px;
  padding: 1rem;
  position: relative;
  height: 100%;

  .pdf-content {
    display: flex;
    flex-direction: column;
    gap: 1rem;
    position: relative;
    z-index: 1;
    height: 100%;

    .pdf-title {
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 12px 16px;
      gap: 1rem;
      border: 1px solid black;
      border-radius: 4px;

      .title-1,
      .title-2 {
        display: flex;
        gap: 1rem;
      }

      & svg {
        stroke: black;
        stroke-width: 0.01px;
        transform: scale(1.75);
      }

      .hr {
        height: 24px;
        width: 2px;
        background-color: black;
      }
    }

    & h1 {
      font-size: 24px;
      text-align: center;
      margin: 0;
      padding: 0;
    }

    .workout-to-print-day {
      break-inside: avoid;

      .workout-to-print-day-title {
        margin-top: 1rem;
        font-size: 14px;
        margin-bottom: 0.25rem;
      }

      .workout-to-print-day-exercises {
        display: grid;
        grid-template-columns: repeat(auto-fill, minmax(100px, 110px));
        gap: 1rem;

        .workout-to-print-day-exercise {
          border: 1px solid rgba(0, 0, 0, 0.25);
          border-radius: 4px;
          background-color: white;

          & img {
            width: 100%;
            border-radius: 4px;
          }

          .exercise-texts {
            padding: 0 6px 6px 6px;
            display: flex;
            flex-direction: column;
            gap: 4px;

            & h3 {
              font-size: 9px;
            }
          }
        }
      }
    }
  }

  &::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-image: var(--background-image);
    background-size: cover;
    filter: grayscale(100%);
    opacity: 0.1;
    z-index: 0;
  }
}

I greatly appreciate any help.

I know my code isn't the best, but I'm still learning :)

siaikin commented 1 year ago

Unfortunately, I did not find a way to automatically fill the paper with a background image.

But you can calculate the total height in advance and set it in CSS to ensure that the last page can be fully filled. In this way, the background image can also fill the paper.

Tpuljak commented 1 year ago

You can add an extra div element (<div class="brochure-background"/>) that will act as the background to fill the entire page.

  div.brochure-background {
    position: fixed;
    z-index: -1;
    top: var(--brochure-header-height);
    width: 100%;
    height: calc(100% - var(--brochure-footer-height) - var(--brochure-header-height));
    background-color: hsl(var(--background));
    page-break-after: always;
  }

Just make sure it's inserted below the footer element (if you have it).

titoworlddev commented 1 year ago

You can add an extra div element (<div class="brochure-background"/>) that will act as the background to fill the entire page.

  div.brochure-background {
    position: fixed;
    z-index: -1;
    top: var(--brochure-header-height);
    width: 100%;
    height: calc(100% - var(--brochure-footer-height) - var(--brochure-header-height));
    background-color: hsl(var(--background));
    page-break-after: always;
  }

Just make sure it's inserted below the footer element (if you have it).

That's perfect friend!!

Thank you very much, you have helped me a lot. I would even give you a kiss hahaha

It would also be interesting to add an option to the package that would do it automatically, but for the moment it works fine for me.

Thanks again.

MatthewHerbst commented 1 year ago

It would also be interesting to add an option to the package that would do it automatically

PRs are always welcome, though not sure we would want to introduce opinionated style props. However, would be happy to see something about this added to the README style tips section

titoworlddev commented 1 year ago

It would also be interesting to add an option to the package that would do it automatically

PRs are always welcome, though not sure we would want to introduce opinionated style props. However, would be happy to see something about this added to the README style tips section

I'll be happy to add in a PR as soon as I have time. At the moment this website takes up all my time but I keep it in mind.

siaikin commented 1 year ago
    top: var(--brochure-header-height);

You can add an extra div element (<div class="brochure-background"/>) that will act as the background to fill the entire page.

  div.brochure-background {
    position: fixed;
    z-index: -1;
    top: var(--brochure-header-height);
    width: 100%;
    height: calc(100% - var(--brochure-footer-height) - var(--brochure-header-height));
    background-color: hsl(var(--background));
    page-break-after: always;
  }

Just make sure it's inserted below the footer element (if you have it).

Good job!! This is really a brilliant approach. I will study later how it was implemented.

titoworlddev commented 1 year ago
    top: var(--brochure-header-height);

Puede agregar un elemento div adicional ( <div class="brochure-background"/>) que actuará como fondo para llenar toda la página.

  div.brochure-background {
    position: fixed;
    z-index: -1;
    top: var(--brochure-header-height);
    width: 100%;
    height: calc(100% - var(--brochure-footer-height) - var(--brochure-header-height));
    background-color: hsl(var(--background));
    page-break-after: always;
  }

Solo asegúrese de que esté insertado debajo del elemento de pie de página (si lo tiene).

¡¡Buen trabajo!! Este es realmente un enfoque brillante. Más adelante estudiaré cómo se implementó.

@siaikin

Actually my solution was to use the same thing that @Tpuljak said, but since I was not clear where the var() that he uses in the css come from, I did it with values.

Here my jsx

import { useContext, useEffect, useState } from 'react';
import { useGetExercises } from '../../services/getExercises';
import './WorkoutToPrint.css';
import { LocalStorageContext } from '../../contexts/LocalStorage/LocalStorageContext';
import BarbellIcon from '../Icons/BarbellIcon';
import GymPersonIcon from '../Icons/GymPersonIcon';
import { useAppLanguageStore } from '../../stores/appLanguageStore';
import { languages } from '../../languages/languages';

/**
 *
 * @param {Object} props
 * @param {React.MutableRefObject<any>} props.useRef Ref of the object to handlePrint
 */
export default function WorkoutToPrint({ useRef }) {
  const { pdfImageURL, userClients, currentClientIndex, currentClient } =
    useContext(LocalStorageContext);

  const { getExercises } = useGetExercises();

  const { appLanguage } = useAppLanguageStore(state => state);
  const weekDays = languages[appLanguage].weekDays;

  /**
   * @type {[Workout, React.Dispatch<React.SetStateAction<Workout>>]}
   */
  const [currentWorkout, setCurrentWorkout] = useState(
    userClients[currentClientIndex].workouts[
      userClients[currentClientIndex].currentWorkoutIndex
    ]
  );

  useEffect(() => {
    setCurrentWorkout(
      userClients[currentClientIndex].workouts[
        userClients[currentClientIndex].currentWorkoutIndex
      ] || {}
    );
  }, [userClients]);

  return (
    <div
      ref={useRef}
      className="workout-to-print"
      // @ts-ignore
      style={{ '--background-image': `url(${pdfImageURL})` }}
    >
      <div className="pdf-content">
        <div className="pdf-title">
          <div className="title-1">
            <BarbellIcon />
            <div className="hr"></div>
            {/* <h1>{currentClient.clientName}</h1> */}
          </div>

          <h1>{currentClient.clientName}</h1>
          {/* <div className="hr"></div> */}

          <div className="title-2">
            {/* <h1>{currentWorkout.workoutName}</h1> */}
            <div className="hr"></div>
            <GymPersonIcon />
          </div>
        </div>

        {currentWorkout.workoutDays
          ? currentWorkout.workoutDays.map((day, index) => (
              <div className="workout-to-print-day" key={day.dayName + index}>
                <h2 className="workout-to-print-day-title">{`${weekDays[index]} - ${day.dayName}`}</h2>

                <div className="workout-to-print-day-exercises">
                  {day.dayExercises.map((exercise, index) => {
                    const exer = getExercises().find(
                      exer => exer.id === exercise.exerciseId
                    );

                    return (
                      <div
                        key={exercise.exerciseId + index}
                        className="workout-to-print-day-exercise"
                      >
                        <img alt="img" src={exer?.gifUrl} />

                        <div className="exercise-texts">
                          <h3>
                            {exer?.name
                              // @ts-ignore
                              .capitalize()}
                          </h3>
                          <p>
                            Target:{' '}
                            {exer?.target
                              // @ts-ignore
                              .capitalize()}
                          </p>
                          <p>Sets: {exercise.sets}</p>
                          <p>Reps: {exercise.reps}</p>
                        </div>
                      </div>
                    );
                  })}
                </div>
              </div>
            ))
          : null}

        <div className="brochure-background"></div>
      </div>
    </div>
  );
}

And here my css

@media print {
  @page {
    size: portrait;
  }

  html,
  body {
    margin: 0 !important;
    padding: 0 !important;
    background-color: white;
  }
}

.workout-to-print {
  color: #000;
  flex-direction: column;
  gap: 1rem;
  font-size: 9px;
  padding: 1rem;
  position: relative;
  height: 100%;

  .pdf-content {
    display: flex;
    flex-direction: column;
    gap: 1rem;
    position: relative;
    z-index: 1;
    height: 100%;

    .pdf-title {
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 12px 16px;
      gap: 1rem;
      border: 1px solid black;
      border-radius: 4px;

      .title-1,
      .title-2 {
        display: flex;
        gap: 1rem;
      }

      & svg {
        stroke: black;
        stroke-width: 0.01px;
        transform: scale(1.75);
      }

      .hr {
        height: 24px;
        width: 2px;
        background-color: black;
      }
    }

    & h1 {
      font-size: 24px;
      text-align: center;
      margin: 0;
      padding: 0;
    }

    .workout-to-print-day {
      break-inside: avoid;

      .workout-to-print-day-title {
        margin-top: 1rem;
        font-size: 14px;
        margin-bottom: 0.25rem;
      }

      .workout-to-print-day-exercises {
        display: grid;
        grid-template-columns: repeat(auto-fill, minmax(100px, 110px));
        gap: 1rem;

        .workout-to-print-day-exercise {
          border: 1px solid rgba(0, 0, 0, 0.25);
          border-radius: 4px;
          background-color: white;

          & img {
            width: 100%;
            border-radius: 4px;
          }

          .exercise-texts {
            padding: 0 6px 6px 6px;
            display: flex;
            flex-direction: column;
            gap: 4px;

            & h3 {
              font-size: 9px;
            }
          }
        }
      }
    }

    .brochure-background {
      position: fixed;
      z-index: -1;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      background-color: transparent;
      page-break-after: always;

      &::before {
        content: '';
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background-image: var(--background-image);
        background-size: cover;
        filter: grayscale(100%);
        opacity: 0.05;
        z-index: 0;
      }
    }
  }
}

I hope this can be of some use to you and to everyone who comes in looking for help.