1
  1. We build this web control to send commands to IPC(IP Camera). Then the IPC could rotate.

  2. It's a web component and I don't know the name. It looks like this:

    enter image description here

  3. When the mouse hovers on one of the buttons, the highlighted area is like:

    enter image description here

  4. Pure css is preferable, SVG is also acceptable.

  5. I can accomplish step 2. But have totally no idea about step 3.

  6. My code for step 2 is like the following. I'm using grommet. Go to https://cache-hit-shanghai.github.io/jujiuipcappui/device/streaming to see the demo. The whole code base is https://github.com/Cache-Hit-Shanghai/jujiuipcappui.

export function PanControl() {
  const targetRef = useRef();
  const [, setShowDrop] = useState(false);
  useEffect(() => setShowDrop(true) , []);
  return (
    <Box align='center'>
      <Stack anchor='center'>
        <Box background='placeholder' round='full' width='small' height='small' ref={targetRef} />
        <Box background='active' round='full' width='xxsmall' height='xxsmall' />
      </Stack>
      {
        targetRef.current && (
          <>
            <Drop align={{ top: 'top' }} target={targetRef.current} stretch={false} plain>
              <Button icon={<CaretUpFill />} />
            </Drop>
            <Drop align={{ bottom: 'bottom' }} target={targetRef.current} stretch={false} plain>
              <Button icon={<CaretDownFill />} />
            </Drop>
            <Drop align={{ left: 'left' }} target={targetRef.current} stretch={false} plain>
              <Button icon={<CaretLeftFill />} />
            </Drop>
            <Drop align={{ right: 'right' }} target={targetRef.current} stretch={false} plain>
              <Button icon={<CaretRightFill />} />
            </Drop>
          </>
        )
      }
    </Box>
  );
}
Rob
  • 14,746
  • 28
  • 47
  • 65
Jim Jin
  • 1,249
  • 1
  • 15
  • 28

2 Answers2

0

Finally, I build a better pan control. Here is the code of the component:

import { Box } from 'grommet';
import { CaretLeftFill } from 'grommet-icons';

function SectorBox({ angle, onClick }) {
  return (
    <Box
      focusIndicator={false}
      width='50%' height='50%' onClick={onClick}
      style={{
        position: 'absolute',
        transform: `rotate(${angle}deg)`,
        transformOrigin: '100% 100%',
      }}
    >
      <Box
        width='calc(100% - 1px)' height='calc(100% - 1px)' align='center'
        justify='center' background='dark-3' round={{ size: 'full', corner: 'top-left' }}
        style={{
          WebkitMaskImage: 'radial-gradient(circle farthest-side at bottom right, transparent 40%, #000 40%)'
        }}
      >
        <CaretLeftFill style={{ transform: 'rotate(45deg)' }} />
      </Box>
    </Box>
  );
}

function Circle() {
  return (
    <Box background='dark-3' round='full' width='calc(40% - 3px)' height='calc(40% - 3px)'
      margin='auto'
      style={{
        position: 'absolute',
        left: 0,
        right: 0,
        top: 0,
        bottom: 0,
      }}
    />
  );
}

export function PanControl({ size='medium', ...props }) {
  return (
    <Box flex={false} width={size} height={size} style={{ position: 'relative' }} {...props}>
      <SectorBox angle={45} onClick={() => console.log('up')} />
      <SectorBox angle={135} onClick={() => console.log('right')} />
      <SectorBox angle={225} onClick={() => console.log('down')} />
      <SectorBox angle={315} onClick={() => console.log('left')} />
      <Circle />
    </Box>
  );
}

Or clone the code here: https://github.com/jim-king-2000/testGrommet

Here are some main ideas:

  1. Make circular sector.
    1. Make 4 square divs which the height/width is 50% - 1px.
    2. Turn the left and top border of the square div to round.
    3. Rotate these divs 45deg, 135deg, 225deg, 315deg respectively.
  2. Cut the right-bottom corner of the circular sector.
    1. Make a radial gradient from right-bottom corner, 0 ~ 40% transparent, 40% ~ 100% white.
    2. Mask the radial gradient.
  3. Add a small circle in the center. The size is 100% - 1px.
Jim Jin
  • 1,249
  • 1
  • 15
  • 28
-2

We implemented this by referencing How can I make a circular sector using CSS.

And the code is here: https://github.com/Cache-Hit-Shanghai/jujiuipcappui

The relevant code is:

import styles from "./components.module.css";

export function PanControl({ onPanClick = () => {} }) {
    return (
        <Box responsive={false} className={styles.sector}>
            {[
                {
                    direction: "top",
                    Icon: CaretUpFill,
                },
                {
                    direction: "right",
                    Icon: CaretRightFill,
                },
                {
                    direction: "bottom",
                    Icon: CaretDownFill,
                },
                {
                    direction: "left",
                    Icon: CaretLeftFill,
                },
                {
                    direction: "center",
                    isCenter: true,
                },
            ].map(({ className, direction, isCenter = false, Icon }) => (
                <Box
                    className={`${isCenter ? "" : styles.box} ${
                        styles[direction]
                    }`}
                    key={className}
                    justify="center"
                    align="center"
                    onClick={() => {
                        onPanClick({
                            direction,
                        });
                    }}
                >
                    {Icon && (
                        <Icon
                            className={`${styles.icon} ${
                                styles[direction + "Icon"]
                            }`}
                        ></Icon>
                    )}
                </Box>
            ))}
        </Box>
    );
}


.sector {
    position: relative;
    width: 200px;
    height: 200px;
    margin: 0 auto;
    /* border: 2px solid #AAAAAA; */
    border-radius: 50%;
    background: #999;
    overflow: hidden;
    outline: none!important;
    box-shadow: none!important;
}
.sector .box {
    position: absolute;
    width: 200px;
    height: 200px;
    border-radius: 50%;
    clip: rect(0px, 99px, 99px, 0);
    z-index: 9;
    background: #AAAAAA;
    outline: none!important;
    box-shadow: none!important;
}
.sector .box,
.sector .center {
    transition: 0.5s;
    cursor: pointer;

}
.sector .box:hover,
.sector .center:hover {
    background: #DDD;
    overflow: hidden;
}
.sector .top {
    transform: rotate(45deg);
}
.sector .right {
    transform: rotate(135deg);
}
.sector .bottom {
    transform: rotate(-135deg);
}
.sector .left {
    transform: rotate(-45deg);
}

.icon{
    position: absolute;
}

.topIcon{
    transform: translate(-50px,-50px) rotate(-45deg);

}

.rightIcon{
    transform: translate(-50px,-50px) rotate(-135deg);
}

.bottomIcon{
    transform: translate(-50px,-50px) rotate(135deg);
}

.leftIcon{
    transform: translate(-50px,-50px) rotate(45deg);
}

.sector .center {
    width: 90px;
    height: 90px;
    position: absolute;
    left: 54px;
    top: 54px;
    border-radius: 50%;
    z-index: 99;
    background-color: #c8c8c8;
    outline: none!important;
    box-shadow: none!important;
        border: 2px solid #999;
}
Jim Jin
  • 1,249
  • 1
  • 15
  • 28