sisterAn / JavaScript-Algorithms

基础理论+JS框架应用+实践,从0到1构建整个前端算法体系
5.45k stars 626 forks source link

用 React 实现一个信号灯(交通灯)控制器 #155

Open sisterAn opened 3 years ago

sisterAn commented 3 years ago
/** 1. 信号灯控制器
用 React 实现一个信号灯(交通灯)控制器,要求:
1. 默认情况下,
  1.1. 红灯亮20秒,并且最后5秒闪烁
  1.2. 绿灯亮20秒,并且最后5秒闪烁
  1.3. 黄灯亮10秒
  1.4. 次序为 红-绿-黄-红-绿-黄
2. 灯的个数、颜色、持续时间、闪烁时间、灯光次序都可配置,如:
   lights=[{color: '#fff', duration: 10000, twinkleDuration: 5000}, ... ]
*/

import React from 'react'
import ReactDOM from 'react-dom'
class TrafficLightItem extends React.Component {
}
zhang0ZGC commented 3 years ago

我的想法是灯的闪烁就用了CSS的动画加上设定的延迟时间🤣利用 CSS 变量设置灯的颜色及闪烁的时间点。

@keyframes twinkle {
  0% {
    /*background-color: #10a54a;*/
    background-color: var(--color);
  }
  50% {
    background-color: #000;
  }
  100% {
    /*background-color: #10a54a;*/
    background-color: var(--color);
  }
}

.light {
  background-color: #000;
  width: 60px;
  height: 60px;
  border-radius: 30px;
  margin: 4px;
}

.light.active {
  background-color: var(--color);
  animation: twinkle 1s ease-in-out infinite var(--twinkle-delay);
}

然后给灯设置上CSS变量:

type LightConf = {
  color: string;
  duration: number;
  twinkleDuration?: number;
};

type TrafficLightItemProps = {
  active?: boolean;
} & LightConf;

class TrafficLightItem extends React.Component<TrafficLightItemProps> {
  render() {
    const { active, color, duration, twinkleDuration } = this.props;
    let styles: any = {
      "--color": color,
      "--twinkle-delay": duration - (twinkleDuration || 0) + "ms"
    };
    return <div className={`light ${active ? "active" : ""}`} style={styles} />;
  }
}

然后整个信号灯的框架组件,设置灯的配置,内部状态记录当前亮的灯的序号,并在组件挂在后,利用 setTimeout 循环修改当前灯的序号

type TrafficLightState = { currentIdx: number; lights: LightConf[] };
class TrafficLight extends React.Component<{}, TrafficLightState> {
  constructor(props: any) {
    super(props);

    this.timer = null;

    this.state = {
      lights: [
        { color: "red", duration: 20000, twinkleDuration: 5000 },
        { color: "green", duration: 20000, twinkleDuration: 5000 },
        { color: "yellow", duration: 10000 }
      ],
      currentIdx: 0
    };
  }

  componentDidMount() {
    this.startTimer();
  }

  componentWillUnmount() {
    clearTimeout(this.timer);
  }

  startTimer = () => {
    const { lights, currentIdx } = this.state;

    const nextIdx: number = lights.length - 1 > currentIdx ? currentIdx + 1 : 0;
    this.timer = setTimeout(() => {
      this.setState({ currentIdx: nextIdx });

      this.startTimer();
    }, lights[currentIdx].duration);
  };

  render() {
    const { currentIdx, lights } = this.state;

    return (
      <div>
        {lights.map((light, k) => (
          <TrafficLightItem key={k} {...light} active={currentIdx === k} />
        ))}
      </div>
    );
  }
}

也不知道有没有跑题,感觉会有点问题。。如果js很卡了的话,最后黄灯可能会闪。。

挫代码😂 https://codesandbox.io/s/new-rain-9pdzn

xiaoerwen commented 3 years ago

`import React from 'react' import ReactDOM from 'react-dom' class TrafficLightItem extends React.Component {

constructor(props) {
    super(props);
    this.state = {
        lights: [
            // 红灯,亮2s,闪烁500ms
            {color: '#ff4500', duration: 2000, twinkleDuration: 500, isLighted: false},
            // 黄灯,亮2s,闪烁500ms
            {color: '#FFD700', duration: 2000, twinkleDuration: 500, isLighted: false},
            // 绿灯,亮1s
            {color: '#32CD32', duration: 1000, isLighted: false}
        ]

    }
}

/**
 * 灯的颜色和数据的映射关系
 *
 * @param {string} color
 */
getLightsMap(color) {
    const maps = {
        red: 0,
        yellow: 1,
        green: 2
    };
    return maps[color];
}

/**
 * 为信号灯添加背景色
 *
 * @param {string} color
 */
addLightedStyle(color) {
    const light = this.state.lights[this.getLightsMap(color)];
    return light.isLighted ? light.color : '#fff';
}

render() {
    return (
        <div>
            <div className="light" style={{backgroundColor: this.addLightedStyle('red')}}>red</div>
            <div className="light" style={{backgroundColor: this.addLightedStyle('yellow')}}>yellow</div>
            <div className="light" style={{backgroundColor: this.addLightedStyle('green')}}>green</div>
        </div>
    );
}

componentDidMount() {
    this.start();
}

// 启动信号灯变化
start() {
    // 播放顺序为 红->黄->绿->红->黄->绿
    let playArr = [0, 1, 2, 0, 1, 2];
    // 每个信号灯执行结束,是否进入下一个信号灯播放,存入回调函数
    let len = playArr.length;
    let cbs = new Array(len);
    for (let i = len - 1; i > 0; i--) {
        cbs[i - 1] = () => this.setLightedTime(playArr[i], cbs[i]);
    }
    // 开始第一个灯的播放
    this.setLightedTime(playArr[0], cbs[0]);
}

/**
 * 变换灯的颜色
 *
 * @param {number} colorIdx 
 */
toggleLighted(colorIdx) {
    let light = this.state.lights[colorIdx];
    let value = Object.assign(
        {},
        light,
        {isLighted: !light.isLighted}
    );
    this.setState({
        lights: this.state.lights.map((item, key) => key === colorIdx ? value : item)
    });
}

/**
 * 设置亮灯时长
 *
 * @param {number} colorIdx 
 * @param {Function} callback 
 */
setLightedTime(colorIdx, callback) {
    // 先亮灯
    this.toggleLighted(colorIdx);

    // 设置亮灯时长
    let lightTime = this.state.lights[colorIdx].duration;
    setTimeout(() => {
        // 是否闪烁
        let blingTime = this.state.lights[colorIdx].twinkleDuration;
        if (blingTime && callback) {
            this.blingLightTime(colorIdx, blingTime, callback);
        }
        // 绿灯不闪烁,直接熄灯
        else if (callback) {
            this.toggleLighted(colorIdx);
            callback();
        }
    }, lightTime);
}

/**
 * 闪烁灯
 *
 * @param {number} colorIdx 
 * @param {number} blingTime 
 * @param {Function} callback 
 */
blingLightTime(colorIdx, blingTime, callback) {
    // 通过切换背景颜色来达到闪烁的目的
    let myInterval = setInterval(() => {
        this.toggleLighted(colorIdx);
    }, 100);
    setTimeout(() => {
        clearInterval(myInterval);
        callback();
    }, blingTime);
}

} `