倒计时功能简易教程

小程序开发相关产品技术讨论,包括面板、智能小程序、React Native、Ray跨端框架、Panel SDK、微信小程序、小程序开发工具(IDE)及其他开发技术相关等话题


Post Reply
crisiron
Posts: 128

核心部件:
倒计时动态圆环

circle
circle

Code: Select all

.countdown-opened-wrapper {
  display: flex;
  justify-content: center;
  align-items: center;

  .countdown-opened-text-button-wrapper {
    position: absolute;
    bottom: 38px;
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    justify-content: center;

.countdown-opened-text-button {
  width: 686rpx;
  font-weight: 400;
  font-size: 32rpx;
  background: rgba(255, 255, 255, 0.1);
  border-radius: 48rpx;
  color: #DA3737;
}
  }

  .countdown-opened-circle {
    position: relative;
    margin-top: 30%;
    margin-bottom: 33%;
    border-radius: 240rpx;
    height: 480rpx;
    width: 480rpx;
    background: #171515;

.countdown-opened-circle-tip {
  width: 480rpx;
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  justify-content: center;
  align-items: baseline;

  .num-text {
    font-weight: 700;
    font-size: 80rpx;
    color: #FFFFFF;
    margin-right: 4rpx;
  }

  .tip-text {
    position: relative;
    top: -10rpx;
    font-weight: 400;
    font-size: 28rpx;
    color: rgba(255, 255, 255, 0.5);
  }
}

.countdown-opened-circle-button {

  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);

  font-size: 24rpx;
  font-weight: 400;
  line-height: 32rpx;
  margin-top: 80rpx;

  .countdown-opened-circle-text {
    font-style: normal;
    font-weight: 400;
    font-size: 24rpx;
    color: #999999;
  }
}
  }
}


Code: Select all

import React, { CSSProperties, useEffect } from 'react';
import { View, Button, Text } from '@ray-js/ray';

import Strings from '../../i18n';
import styles from './index.module.less';

type Props = {
  isDarkTheme: boolean;
  maxSecond: number;
  currentSecond: number;
  onClose: () => void;
  onResetting: () => void;
};

const CountdownCircle = (props: Props) => {
  const { currentSecond, maxSecond, isDarkTheme = true, onClose, onResetting } = props;
  const countdown = currentSecond;

  useEffect(() => {
    if (currentSecond <= 0) {
      onResetting && onResetting();
    }
  }, [currentSecond]);

  const handleClose = () => {
    onClose && onClose();
  };

  const passColor = isDarkTheme ? '#1082FE' : '#3060c6';
  const notPassColor = isDarkTheme ? '#272727' : '#dddddd';

  const renderCircles = () => {
    const circleNum = 120;
    const diff = maxSecond - countdown;
    const passedNum = maxSecond > 0 ? Math.floor((diff / maxSecond) * circleNum) : 0;

const circleList = new Array(circleNum).fill(0).map((_, idx) => {
  const styles: CSSProperties = {
    position: 'absolute',
    left: '50%',
    top: '50%',
    transform: 'translate(-50%)',
  };
  const radius = 128;
  const deg = idx * (360 / circleNum);
  const paddingX = Math.sin((deg / 180) * Math.PI) * radius;
  const paddingY = -Math.cos((deg / 180) * Math.PI) * radius;
  let colorStyle = notPassColor;
  if (passedNum > idx) {
    colorStyle = passColor;
  }
  return (
    <View key={idx} style={styles}>
      <View
        style={{
          position: 'relative',
          top: '-2.5px',
          transform: `translateX(${paddingX}px) translateY(${paddingY}px) rotate(${deg}deg)`,
          backgroundColor: `${colorStyle}`,
          height: '5px',
          width: '1px',
        }}
      />
    </View>
  );
});
return circleList;
  };

  const getTipText = (secondRes: number) => {
    const hourTxt = Strings.getLang('hour');
    const minuteTxt = Strings.getLang('minute');
    const secondTxt = Strings.getLang('second');
    const SECOND = 60;

const hour = Math.floor(secondRes / 3600);
const minute = Math.floor((secondRes % 3600) / SECOND);
const second = secondRes % SECOND;
const tipTextStyle = {
  color: isDarkTheme ? 'rgba(255, 255, 255, 0.5)' : 'rgba(0, 0, 0, 0.5)',
};
const numTextStyle = {
  color: isDarkTheme ? 'rgba(255, 255, 255, 1)' : 'rgba(0, 0, 0, 1)',
};

return (
  <>
    {!!hour && (
      <Text className={styles['num-text']} style={numTextStyle}>
        {hour < 10 ? `0${hour}` : hour}
      </Text>
    )}
    {!!hour && (
      <Text className={styles['tip-text']} style={tipTextStyle}>
        {hourTxt}
      </Text>
    )}
    <Text
      className={styles['num-text']}
      style={{
        marginLeft: '14rpx',
        ...numTextStyle,
      }}
    >
      {minute < 10 ? `0${minute}` : minute}
    </Text>
    <Text className={styles['tip-text']} style={tipTextStyle}>
      {minuteTxt}
    </Text>
    <Text
      className={styles['num-text']}
      style={{
        marginLeft: '14rpx',
        ...numTextStyle,
      }}
    >
      {second < 10 ? `0${second}` : second}
    </Text>
    <Text className={styles['tip-text']} style={tipTextStyle}>
      {secondTxt}
    </Text>
  </>
);
  };

  if (typeof props.maxSecond !== 'number' || props.maxSecond <= 0) {
    return null;
  }
  const handleResetting = () => {
    onResetting && onResetting();
  };

  const countdownOpenedCircleStyle = {
    background: isDarkTheme ? 'rgba(0, 0, 0, 0.02)' : 'rgba(255, 255, 255, 0.02)',
  };
  const countdownOpenedCircleTextStyle = {};
  const countdownOpenedTextButtonStyle = {
    background: isDarkTheme ? 'rgba(255, 255, 255, .1)' : 'rgba(255, 255, 255, 1)',
  };

  return (
    <View className={styles['countdown-opened-wrapper']}>
      <View className={styles['countdown-opened-circle']} style={countdownOpenedCircleStyle}>
        {renderCircles()}
        <View className={styles['countdown-opened-circle-tip']}>{getTipText(countdown)}</View>
        <View className={styles['countdown-opened-circle-button']} onClick={handleResetting}>
          <Text
            className={styles['countdown-opened-circle-text']}
            style={countdownOpenedCircleTextStyle}
          >
            {Strings.getLang('resetting')}
          </Text>
        </View>
      </View>
      {/* 关闭倒计时 */}
      <View className={styles['countdown-opened-text-button-wrapper']}>
        <Button
          className={styles['countdown-opened-text-button']}
          style={countdownOpenedTextButtonStyle}
          onClick={handleClose}
        >
          {Strings.getLang('closeCountdown')}
        </Button>
      </View>
    </View>
  );
};

export default CountdownCircle;

倒计时 选择页

picker
picker

Code: Select all


.countdown {
  &_container {
    height: 100vh;
    --picker-option-unit-font-size: 24px;
    --picker-background-color: transparent;
    --popup-background-color: transparent;
    --nav-bar-background-color: transparent;
  }
  &_scrollView {
    position: relative;
    height: calc(100vh - env(safe-area-inset-top) - env(safe-area-inset-bottom) - 46px);
  }
  &_picker {
    .smart-picker-column__item {
      font-size: 40px;
      margin-right: 28px;
    }
  }
  &_card-container {
    background-color: var(--app-B3);
  }
  &_placeholder {
    width: 40px;
  }
  &_tip {
    padding: 12px 16px;
    font-size: 14px;
    color: rgba(255, 255, 255, 0.7);
    text-align: center;
  }
  :global(.ty-picker-column) {
    color: rgba(255, 255, 255, 0.9);
  }
  :global(.ty-picker-mask) {
    background: linear-gradient(180deg, rgba(26, 26, 26, 0.9), rgba(26, 26, 26, 0.4));
  }
  &_popup {
    position: relative;
    display: flex;
  }
  &_confirm-button {
    position: absolute;
    bottom: calc(constant(safe-area-inset-bottom) + 24px);
    bottom: calc(env(safe-area-inset-bottom) + 24px);
    width: 100vw;
    height: 48px;
    border-radius: 24px;
    display: flex;
    padding: 0 16px;
  }
}

Code: Select all

<View
        style={{
          backgroundColor: bgColor,
          backgroundImage: `url(${bgImg})`,
        }}
        className={`${prefixClass}-content-container`}

    <NavBar
      leftText={Strings.getLang('cancel')}
      title={Strings.formatValue('countdownTitle', onOffText)}
      onClickLeftText={onCancel}
    />
    <ScrollView refresherTriggered={false} scrollY className={`${prefixClass}_scrollView`}>
      <View className={`${prefixClass}_tip`}>
        {Strings.formatValue('countdownTips', onOffText)}
      </View>
      <View className={`${prefixClass}_picker-wrapper`}>
        <Picker
          columns={columns}
          onChange={handleChange}
          showToolbar={false}
          itemHeight={70}
          className={`${prefixClass}_picker`}
        />
      </View>
      <Button
        className={`${prefixClass}_confirm-button`}
        onClick={handleConfirm}
        size="large"
        round
        color="#3b82f7"
      >
        {Strings.getLang('confirm')}
      </Button>
    </ScrollView>
 </View>

Tags:
Post Reply