A good way to handle @material-ui Skeleton scaling within a variable height grid row?
Asked Answered
T

2

10

I'm trying to display a grid of Avatar images. While in a transition state I would like for a skeleton representation of the Image to appear. For that I'm using @material-ui/lab/Skeleton.

The problem I'm having is that, because my images are set to autoscale within a grid using height: auto, width: 100%, and the height/length of the content displayed under the image varies, there is no set height val that I can pass over to the skeleton component.

You can see the problem this causes if you scale down the width of the sandbox screen. The grid item's height increases causing the circle skeleton to begin morphing into an oval.

Is there a solution here that might give me behavior similar to the image's height: auto, width: 100%?

The full code of what I have so far is below and in the sandbox here: https://codesandbox.io/s/skeleton-scaling-y00cd.

import React from "react";
import { makeStyles } from "@material-ui/core/styles";
import Avatar from "@material-ui/core/Avatar";
import Skeleton from "@material-ui/lab/Skeleton";
import Typography from "@material-ui/core/Typography";
import clsx from "clsx";
import Grid from "@material-ui/core/Grid";

const useStyles = makeStyles(theme => ({
  root: {
    textAlign: "center",
    height: "100%",
    width: "100%"
  },
  title: {
    marginTop: theme.spacing(1)
  },
  avatarRoot: {
    // width: '100%',
    // height: 'auto',
    // minHeight: '273px',
  },
  withTitle: {
    margin: "auto"
  },
  img: {},
  content: {
    lineHeight: "1.4em"
  },
  link: {
    color: "inherit",
    textDecoration: "none"
  },
  icon: {},
  fillContainer: {
    height: `auto`,
    width: `100%`,
    fontSize: "4em"
  },
  container: {
    marginTop: "250px"
  },
  fallback: {
    height: "75%",
    width: "auto"
  },
  loader: {},
  avatarLoader: {
    height: "75%",
    width: "100%"
  },
  titleLoader: {
    width: "60%",
    margin: "auto",
    marginTop: "8px",
    height: "5%"
  },
  contentLoader: {
    width: "40%",
    margin: "auto",
    marginTop: "8px",
    height: "5%"
  },
  testImg: {
    borderRadius: "100%",
    height: "auto",
    width: "100%"
  },
  isLoading: {
    display: "none"
  },
  imgContainer: {
    paddingTop: "100%",
    borderRadius: "100%",
    backgroundPosition: "center",
    backgroundSize: "contain",
    backgroundImage: "url(https://via.placeholder.com/500)"
  }
}));

export default function ImageAvatars() {
  const classes = useStyles();

  return (
    <div className={classes.root}>
      <Grid container spacing={1} className={classes.container}>
        <Grid container item xs={3} spacing={0} direction="column">
          <Avatar
            src={"https://via.placeholder.com/500"}
            variant="circle"
            className={clsx(classes.avatarRoot, {
              [classes.isLoading]: false,
              [classes.withTitle]: true,
              [classes.fallback]: false,
              [classes.fillContainer]: true
            })}
          />
          <Typography className={classes.title}>MUI Avatar</Typography>
          <Typography className={classes.content}>test test test</Typography>
        </Grid>
        <Grid container item xs={3} spacing={0} direction="column">
          <div className={classes.imgContainer} />
          <div>
            <Typography className={classes.title}>background image</Typography>
            <Typography className={classes.content}>test test test</Typography>
          </div>
        </Grid>
        <Grid container item xs={3} spacing={0} direction="column">
          <img
            className={classes.testImg}
            src={"https://via.placeholder.com/500"}
            alt={"test"}
          />
          <div>
            <Typography className={classes.title}>image el</Typography>
            <Typography className={classes.content}>test test test</Typography>
          </div>
        </Grid>
        <Grid container item xs={3} spacing={0} direction="column">
          <Skeleton
            variant="circle"
            className={clsx(classes.avatarLoader, classes.avatarRoot)}
          />
          <Skeleton className={clsx(classes.titleLoader, classes.title)} />
          <Skeleton className={clsx(classes.contentLoader, classes.content)} />
        </Grid>
      </Grid>
    </div>
  );
}
Theoretician answered 23/12, 2019 at 21:45 Comment(0)
C
6

The solution below is based on the article here: https://css-tricks.com/aspect-ratio-boxes/#article-header-id-3

The gist of the solution is to use padding-top expressed as a percentage to create a box with a specific aspect ratio (square in this case). Padding in percentages is based on width even when specifying padding-top or padding-bottom, so a padding-top of 100% produces a padding height equal to the width.

The relevant CSS/JSS is:

  avatarSkeletonContainer: {
    height: 0,
    overflow: "hidden",
    paddingTop: "100%",
    position: "relative"
  },
  avatarLoader: {
    position: "absolute",
    top: 0,
    left: 0,
    width: "100%",
    height: "100%"
  },

This is then used as follows:

        <Grid container item xs={3} spacing={0} direction="column">
          <div className={classes.avatarSkeletonContainer}>
            <Skeleton variant="circle" className={classes.avatarLoader} />
          </div>
          <Skeleton className={clsx(classes.titleLoader, classes.title)} />
          <Skeleton className={clsx(classes.contentLoader, classes.content)} />
        </Grid>

Here's the full code of my modification of your sandbox:

import React from "react";
import { makeStyles } from "@material-ui/core/styles";
import Avatar from "@material-ui/core/Avatar";
import Skeleton from "@material-ui/lab/Skeleton";
import Typography from "@material-ui/core/Typography";
import clsx from "clsx";
import Grid from "@material-ui/core/Grid";

const useStyles = makeStyles(theme => ({
  root: {
    textAlign: "center",
    height: "100%",
    width: "100%"
  },
  title: {
    marginTop: theme.spacing(1)
  },
  withTitle: {
    margin: "auto"
  },
  content: {
    lineHeight: "1.4em"
  },
  link: {
    color: "inherit",
    textDecoration: "none"
  },
  fillContainer: {
    height: `auto`,
    width: `100%`,
    fontSize: "4em"
  },
  container: {
    marginTop: "250px"
  },
  fallback: {
    height: "75%",
    width: "auto"
  },
  avatarSkeletonContainer: {
    height: 0,
    overflow: "hidden",
    paddingTop: "100%",
    position: "relative"
  },
  avatarLoader: {
    position: "absolute",
    top: 0,
    left: 0,
    width: "100%",
    height: "100%"
  },
  titleLoader: {
    width: "60%",
    margin: "auto",
    marginTop: "8px",
    height: "5%"
  },
  contentLoader: {
    width: "40%",
    margin: "auto",
    marginTop: "8px",
    height: "5%"
  },
  testImg: {
    borderRadius: "100%",
    height: "auto",
    width: "100%"
  },
  isLoading: {
    display: "none"
  },
  imgContainer: {
    paddingTop: "100%",
    borderRadius: "100%",
    backgroundPosition: "center",
    backgroundSize: "contain",
    backgroundImage: "url(https://via.placeholder.com/500)"
  }
}));

export default function ImageAvatars() {
  const classes = useStyles();

  return (
    <div className={classes.root}>
      <Grid container spacing={1} className={classes.container}>
        <Grid container item xs={3} spacing={0} direction="column">
          <Avatar
            src={"https://via.placeholder.com/500"}
            variant="circle"
            className={clsx(classes.avatarRoot, {
              [classes.isLoading]: false,
              [classes.withTitle]: true,
              [classes.fallback]: false,
              [classes.fillContainer]: true
            })}
          />
          <Typography className={classes.title}>MUI Avatar</Typography>
          <Typography className={classes.content}>test test test</Typography>
        </Grid>
        <Grid container item xs={3} spacing={0} direction="column">
          <div className={classes.imgContainer} />
          <div>
            <Typography className={classes.title}>background image</Typography>
            <Typography className={classes.content}>test test test</Typography>
          </div>
        </Grid>
        <Grid container item xs={3} spacing={0} direction="column">
          <img
            className={classes.testImg}
            src={"https://via.placeholder.com/500"}
            alt={"test"}
          />
          <div>
            <Typography className={classes.title}>image el</Typography>
            <Typography className={classes.content}>test test test</Typography>
          </div>
        </Grid>
        <Grid container item xs={3} spacing={0} direction="column">
          <div className={classes.avatarSkeletonContainer}>
            <Skeleton variant="circle" className={classes.avatarLoader} />
          </div>
          <Skeleton className={clsx(classes.titleLoader, classes.title)} />
          <Skeleton className={clsx(classes.contentLoader, classes.content)} />
        </Grid>
      </Grid>
    </div>
  );
}

Edit Skeleton scaling

Catheryncatheter answered 23/12, 2019 at 23:4 Comment(0)
F
0

Hello in my case I was able to fix it by adding sx={{ transform: "unset"}}, just removing the transformation will fix it.

<Skeleton width={width} height={height} sx={{ transform: "unset" }} />
Foremost answered 8/8 at 9:20 Comment(1)
never use images for inserting code or error messages. Please replace the image in your answer by a code block with the code itself.Kappel

© 2022 - 2024 — McMap. All rights reserved.