react 实现滑块 checkbox

349 阅读1分钟

demo.gif

本文使用了 mui, Box 和 Stack 都相当于 div, 只是布局组件

import { Box, Stack } from '@mui/material';
import { useCallback } from 'react';
import { flexCenter } from '@utils/constant';
import style from './index.module.scss';

interface CheckboxProps {
  // eslint-disable-next-line no-unused-vars
  onChange: (value: boolean) => void;
  checked: boolean;
  content: [string, string];
}

function Checkbox(props: CheckboxProps) {
  const { checked, content, onChange } = props;

  const checkedChange = useCallback(
    (event: React.ChangeEvent<HTMLInputElement>) => {
      const {
        target: { checked },
      } = event;

      onChange(checked);
    },
    [onChange]
  );

  return (
    <div className={style.checkbox__container}>
      <input
        type="checkbox"
        id={style.checkbox_xyz}
        hidden
        defaultChecked={checked}
        onChange={checkedChange}
      />
      <label htmlFor={style.checkbox_xyz} className={style.knobs}></label>
      <Stack className={style.layer} direction="row">
        // male 和 female
        <Box sx={{ ...flexCenter }}>{content[0]}</Box>
        <Box sx={{ ...flexCenter }}>{content[1]}</Box>
      </Stack>
    </div>
  );
}

Checkbox.defaultProps = {
  checked: false,
};

export default Checkbox;
// 其实就是居中而已
export const flexCenter = {
  display: 'flex',
  alignItems: 'center',
  justifyContent: 'center',
};
@use 'sass:math';

$height: 40px;
$width: 120px;
$p: 2px;
$py: 4px;
$px: 8px;
$blockBackground: #fff;
$layerBackground: #f4f2f2;
$borderRadius: 4px;

$colors: (#c0c4cc, #000);

.checkbox__container {
  display: inline-block;
  position: relative;
  background-color: $layerBackground;
  box-sizing: border-box;
  width: $width;
  height: $height;
  padding: $p;

  // checkbox 滑块
  .knobs {
    cursor: pointer;
    z-index: 2;
    position: absolute;
    top: 50%;
    left: 0;
    transform: translate($p, -50%);
    width: math.div($width - 2 * $p, 2);
    background-color: $blockBackground;
    height: $height - 2 * $p;
    border-radius: $borderRadius;
    transition: all 0.5s ease, left 0.5s cubic-bezier(0.5, 0, 0.5, 1);
  }
   
  // checkbox 底色
  .layer {
    position: relative;
    height: $height - 2 * $p;
    pointer-events: none;
    z-index: 3;

    & > div {
      padding: $py $px;
      box-sizing: border-box;
      width: 50%;
      transition: color 0.5s cubic-bezier(0.5, 0, 0.5, 1);
    }

    // 其实就是两个 div (male 和 female)的样式
    @for $i from 1 through 2 {
      & > div:nth-of-type(#{$i}) {
        color: nth($colors, 3 - $i);
      }
    }
  }

  &,
  & .layer {
    border-radius: $borderRadius;
  }

  #checkbox_xyz {
    // 防止手机端点击出现背景
    -webkit-tap-highlight-color: transparent;
    width: 100%;
    height: 100%;

    // 选中后移动滑块
    &:checked ~ .knobs {
      left: math.div($width - 2 * $p, 2);
    }

    &:active ~ .knobs {
      transform-origin: center left;
      // 点击后滑块变大一点点会感觉有弹性
      transform: translate($p, -50%) scaleX(1.1);
    }

    &:checked:active ~ .knobs {
      transform-origin: center right;
    }

    // 选中后改变字体颜色
    &:checked ~ .layer {
      @for $i from 1 through 2 {
        & > div:nth-of-type(#{$i}) {
          color: nth($colors, $i);
        }
      }
    }
  }
}