'React Native HTML - rendering a linear background gradient

I’m currently HTML overflowing height: 300 a box, where React Native HTML is used. (this package: https://www.npmjs.com/package/react-native-render-html)

this provides the following output when 'overflown' - a slight sharp cutoff of text before the 'Read more' Link.

enter image description here

What I'm trying to achieve with this library, is a linear gradient fading from transparent to white on the last element of the box. An absolutely positioned, bottom 0: gradient that gives the illusion there is more to see, and isn't so harsh a cut off.

In my code below: <scrollfade> test </scrollfade> is the element I'm trying to achieve this through.

You will notice it's custom renderer is defined here:

scrollfade: HTMLElementModel.fromCustomModel({
      tagName: 'scrollfade',
      mixedUAStyles: {
        position: 'absolute',
        width: '100%',
        height: '220px',
        left: 0,
        bottom: 0,
        display: 'flex',
      },
      contentModel: HTMLContentModel.block,
    }),

This however does not seem to facilitate backgroundImages, background gradients or the like to be able to define a style that would potentially achieve that. I tried using from 'react-native-linear-gradient' - just beneath the html renderer, however it isn't within the correct place in the dom to have any impact. i.e. the text harshly cuts off, and then the gradient shows. I'm not sure if its possible to mix the two technologies and use the native component somehow in the html template, I suspect not. Ideas for how to potentially write a custom renderer? for the gradient and define it in the template, or other simpler approaches welcome.

To clarify, I'm looking for a visual gradient solution that allows the text pre-overflow to shine slightly through.

import React, { FunctionComponent } from 'react';
import { StyleSheet, Text, useWindowDimensions, ViewStyle } from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
import RenderHtml, {
  defaultSystemFonts,
  HTMLContentModel,
  HTMLElementModel,
  MixedStyleRecord,
} from 'react-native-render-html';
import { Theme, useTheme } from '../styles/native';

interface Props {
  html: string;
  padding: boolean;
  scrollEnabled: boolean;
  style?: ViewStyle;
  pointerEvents?: 'none' | 'auto' | 'box-none' | 'box-only';
}

// Later on in your styles..
const styles = StyleSheet.create({
  linearGradient: {
    flex: 1,
    height: 100,
  },
});

const tagsStyles: MixedStyleRecord = (theme: Theme) => {
  return {
    body: {
      color: theme.textMid,
      fontFamily: 'OpenSans-Regular',
      background: theme.background,
      overflow: 'hidden',
    },
    a: {
      color: theme.primary,
    },
    h1: {
      color: theme.textDark,
    },
    h2: {
      color: theme.textDark,
    },
    h3: {
      color: theme.textDark,
    },
    h4: {
      color: theme.textDark,
    },
    h5: {
      color: theme.textDark,
    },
    h6: {
      color: theme.textDark,
    },
  };
};

const customHTMLElementModels = (style: any, scrollEnabled: boolean, theme: Theme, padding: boolean) => {
  return {
    template: HTMLElementModel.fromCustomModel({
      tagName: 'template',
      mixedUAStyles: {
        ...style,
        position: 'relative',
        padding: padding ? '0 20px 40px 20px' : '0',
        overflow: scrollEnabled ? 'scroll' : 'hidden',
      },
      contentModel: HTMLContentModel.block,
    }),
    scrollfade: HTMLElementModel.fromCustomModel({
      tagName: 'scrollfade',
      mixedUAStyles: {
        position: 'absolute',
        width: '100%',
        height: '220px',
        left: 0,
        bottom: 0,
        display: 'flex',
      },
      contentModel: HTMLContentModel.block,
    }),
  };
};

const wrapHTMLInBody = (html: string) => {
  return `
      <body>
          <template>
            ${html}
            <scrollfade>test</scrollfade>
          </template>
          
      </body>
  `;
};

const Description: FunctionComponent<Props> = (props) => {
  const { width } = useWindowDimensions();
  const theme = useTheme();
  const systemFonts = [...defaultSystemFonts, 'OpenSans-Regular'];
  const endColor = getTheme() === 'dark' ? 'rgba(0,0,0,0)' : 'rgba(255,255,255,0)';

  return (
    <>
      <RenderHtml
        contentWidth={width}
        source={{
          html: wrapHTMLInBody(
            '<p>Miss polly had a dolly that was sick sick sick</p>Miss polly had a dolly that was sick sick sick</p>Miss polly had a dolly that was sick sick sick</p>Miss polly had a dolly that was sick sick sick</p>Miss polly had a dolly that was sick sick sick</p>Miss polly had a dolly that was sick sick sick</p>Miss polly had a dolly that was sick sick sick</p>Miss polly had a dolly that was sick sick sick</p><p>Miss polly had a dolly that was sick sick sick</p><p>Miss polly had a dolly that was sick sick sick</p><p>Miss polly had a dolly that was sick sick sick</p><p>Miss polly had a dolly that was sick sick sick</p><p>Miss polly had a dolly that was sick sick sick</p>',
          ),
        }}
        tagsStyles={tagsStyles(theme)}
        customHTMLElementModels={customHTMLElementModels(props.style, props.scrollEnabled, theme, props.padding)}
        systemFonts={systemFonts}
      />
    </>
  );
};

export default Description;


Solution 1:[1]

I think what you need is a component custom renderer.

You can define both custom models and custom (component) renderers for one html tag. Something along this:

import React, {FunctionComponent} from 'react';
import {StyleSheet, Text, useWindowDimensions, ViewStyle} from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
import RenderHtml, {
  CustomRendererProps,
  defaultSystemFonts,
  HTMLContentModel,
  HTMLElementModel,
  MixedStyleRecord,
  RenderHTMLProps,
  TChildrenRenderer,
  TBlock,
  TNodeChildrenRenderer,
} from 'react-native-render-html';
import {Theme, useTheme} from '../styles/native';

interface Props {
  html: string;
  padding: boolean;
  scrollEnabled: boolean;
  style?: ViewStyle;
  pointerEvents?: 'none' | 'auto' | 'box-none' | 'box-only';
}

// Later on in your styles..
const styles = StyleSheet.create({
  linearGradient: {
    flex: 1,
    height: 100,
  },
});

const customHTMLElementModels = (
  style: any,
  scrollEnabled: boolean,
  theme: Theme,
  padding: boolean,
) => {
  return {
    template: HTMLElementModel.fromCustomModel({
      tagName: 'template',
      mixedUAStyles: {
        ...style,
        position: 'relative',
        padding: padding ? '0 20px 40px 20px' : '0',
        overflow: scrollEnabled ? 'scroll' : 'hidden',
      },
      contentModel: HTMLContentModel.block,
    }),
    scrollfade: HTMLElementModel.fromCustomModel({
      tagName: 'scrollfade',
      mixedUAStyles: {
        position: 'absolute',
        width: '100%',
        height: '220px',
        left: 0,
        bottom: 0,
        display: 'flex',
      },
      contentModel: HTMLContentModel.block,
    }),
  };
};

const renderers: RenderHTMLProps['renderers'] = {
  scrollfade: ({TDefaultRenderer, ...props}: CustomRendererProps<TBlock>) => {
    return (
      <TDefaultRenderer {...props}>
        <TNodeChildrenRenderer tnode={props.tnode} />
        <LinearGradient style={styles.linearGradient} />
      </TDefaultRenderer>
    );
  },
};

const wrapHTMLInBody = (html: string) => {
  return `
      <body>
          <template>
            ${html}
            <scrollfade>test</scrollfade>
          </template>
          
      </body>
  `;
};

const Description: FunctionComponent<Props> = (props) => {
  const {width} = useWindowDimensions();
  const theme = useTheme();
  const systemFonts = [...defaultSystemFonts, 'OpenSans-Regular'];

  return (
    <>
      <RenderHtml
        contentWidth={width}
        source={{
          html: wrapHTMLInBody(
            '<p>Miss polly had a dolly that was sick sick sick</p>Miss polly had a dolly that was sick sick sick</p>Miss polly had a dolly that was sick sick sick</p>Miss polly had a dolly that was sick sick sick</p>Miss polly had a dolly that was sick sick sick</p>Miss polly had a dolly that was sick sick sick</p>Miss polly had a dolly that was sick sick sick</p>Miss polly had a dolly that was sick sick sick</p><p>Miss polly had a dolly that was sick sick sick</p><p>Miss polly had a dolly that was sick sick sick</p><p>Miss polly had a dolly that was sick sick sick</p><p>Miss polly had a dolly that was sick sick sick</p><p>Miss polly had a dolly that was sick sick sick</p>',
          ),
        }}
        renderers={renderers}
        customHTMLElementModels={customHTMLElementModels(
          props.style,
          props.scrollEnabled,
          theme,
          props.padding,
        )}
        systemFonts={systemFonts}
      />
    </>
  );
};

export default Description;

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Christos Lytras