How to set pseudo elements properties dynamically?

Viewed 772

I am trying to overlay a watermark on a HTML5 video, and depending on the user selection, set the position of the watermark on the video.

CSS

/*Video.module.css*/

.wrapper {
    position: relative;
    height: 100%;
}

.wrapper::before {
    display: block;
    position: absolute;
    top: 2%;
    left: 2%;
    height: 18%;
    width: 18%;
    content: '';
    background: 'red';
}

JSX

import styles from 'styles/Video.module.css';

const Video = ({ src }) => {
    return (
        <div class={styles.wrapper}>
            <video src={src}>
                <track type='captions' />
            </video>
        </div>
    );
};

With the current styles, the watermark is positioned at the top-left of the video. I want to allow the user to select the position.

Currently, my approach is to create a css class selector for each position like below and use useState hook to set the class based on user selection.

CSS

.wrapper-top-right {
    position: relative;
    height: 100%;
}

.wrapper-top-right::before {
    top: 2%
    right: 2%
    ....
}

.wrapper-bottom-right {
    ....
}

.wrapper-bottom-right::before {
    bottom: 2%;
    right: 2%;
    ....
}

JSX

import styles from 'styles/Video.module.css';
import React, {useState} from 'react';

const Video = ({ src }) => {
    //Values: 'top-left', 'bottom-left', 'top-right', 'bottom-right'
    const [position, setPosition] = useState('top-left'); 

    return (
        <div class={`styles[${position}]`}>
            <video src={src}>
                <track type='captions' />
            </video>
            /** Button with code to set position.... **/
        </div>
    );
};

But this is definitely not ideal due to the repeated code within the CSS file. Would like to know if there is way to set the properties (in this case, top, bottom, left, right) dynamically so that I only need a single class selector.

2 Answers

You can use CSS custom properties set for host element.

I don't speak React, so quick vanilla POC:

.wrapper::before {
  position: absolute;
  top: var(--before-top, 0%);
  left: var(--before-left, 0%);
  background: red;
  color: white;
  content: var(--before-content, '?');
}
.wrapper {
  position: relative;
  height: 100px;
  outline: #0FF6 solid;
  outline-offset: -2px;
}
label { display: block; }
<label>Top: <input type="range" value="0" min="0" max="100" 
 oninput="w.style.setProperty('--before-top', this.value + '%')"></label>
<label>Left: <input type="range" value="0" min="0" max="100" 
 oninput="w.style.setProperty('--before-left', this.value + '%')"></label>
<label>Content: <input type="text" value="?" 
 oninput="w.style.setProperty('--before-content', `'${this.value}'`)"></label>
<div class="wrapper" id="w"></div>

Set a --custom-property on the parent element. I'll change the values with :hover here, but you should be able to see how it could be done within a style attribute as well:

div {

  /* Important bits */
  --watermarkBottom: 0;
  --watermarkTop: initial;

  /* Boilerplate */
  width: 100px;
  height: 100px;
  background-color:red;
  position: relative;
}

div:hover {
  /* Important bits */
  --watermarkBottom: initial;
  --watermarkTop: 0;
}

div::after {
  /* Important bits */
  bottom: var(--watermarkBottom);
  top: var(--watermarkTop);

  /* Boilerplate */
  content: 'watermark';
  background-color:blue;
  color:white;
  position: absolute;
}
<div></div>

Related