// return <ProfitCalculator config={{
// defaultValue: 1000,
// rate: 30,
// min: 1000,
// max: 30000,
// step: 100,
// }}
// />;
// eslint-disable-next-line import/no-extraneous-dependencies
import React, { PureComponent } from 'react';
import './index.less';
// eslint-disable-next-line import/no-extraneous-dependencies
import cn from 'classnames';
import { connect } from '@alipay/bigfish/sdk';
const FPS = 60;
const STEP_WIDTH = 100;
const acc = 1000;
const doAnimation = fn => window.requestAnimationFrame ? window.requestAnimationFrame(fn) : setTimeout(fn, 1000 / FPS);
const cancelAnimation = identify => {
if (window.cancelAnimationFrame) {
window.cancelAnimationFrame(identify);
} else {
window.clearTimeout(identify);
}
};
@connect(({ context }) => ({ context }))
export class ProfitCalculator extends PureComponent {
position = 0;
lastPosition = -100;
hasStopped = true;
// eslint-disable-next-line react/sort-comp
moveRuler(delta) {
// eslint-disable-next-line react/destructuring-assignment
const { defaultValue, step } = this.props.config;
const nexPos = this.position - delta;
if (nexPos < this.minPos - STEP_WIDTH) {
this.position = this.minPos - STEP_WIDTH;
} else if (nexPos > this.maxPos + STEP_WIDTH) {
this.position = this.maxPos + STEP_WIDTH;
} else {
this.position = nexPos;
}
// 此段逻辑最好能抽离出去
const nextAmount = Math.round(this.position / STEP_WIDTH) * step + defaultValue;
// eslint-disable-next-line react/destructuring-assignment
if (this.state.amount !== nextAmount) {
this.setState({ amount: nextAmount });
}
}
onDeviceOrientation = e => {
const { gamma, beta } = e;
const motivation = gamma * (90 - beta);
if (motivation > 1000 || motivation < -1000) {
// begin drop
this.beginDrop(motivation);
} else {
// stop drop
this.endDrop();
}
};
beginDrop(motivation) {
this.hasStopped = false;
this.draw();
this.motivation = motivation;
if (!this.dropping) {
this.dropping = true;
this.drop(new Date());
}
}
endDrop() {
if (this.dropping) {
this.dropping = false;
if (this.decelerateToken) {
cancelAnimation(this.decelerateToken);
}
this.decelerate(new Date());
}
}
drop(dt) {
if (this.dropping) {
const now = new Date();
const sec = (now - dt) / 1000;
const speed = 1;
const delta = (this.motivation > 0 ? this.motivation - 1000 : this.motivation + 1000) * speed * sec;
this.moveRuler(delta);
this.dropToken = doAnimation(() => {
this.drop(now);
});
}
}
registerMotion() {
window.addEventListener('deviceorientation', this.onDeviceOrientation);
}
// eslint-disable-next-line no-unused-vars
draw(e) {
// draw logic // this.position
if (Math.abs(this.position - this.lastPosition) < 0.1) {
// do nothing because men cannot recognize very little movement
} else {
const { ctx, canvasWidth, canvasHeight, position } = this;
this.lastPosition = position;
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
// eslint-disable-next-line react/destructuring-assignment
const { defaultValue, min, max, step, magic } = this.props.config;
const begin = Math.floor((position - canvasWidth / 2) / STEP_WIDTH) * step + defaultValue;
const end = Math.ceil((position + canvasWidth / 2) / STEP_WIDTH) * step + defaultValue;
ctx.fillStyle = '#bbbbbb';
ctx.font = '24px "PingFang SC"';
for (let cur = begin; cur <= end; cur += (step / 10)) {
if (cur > max || cur < min || cur % step === 0) {
// eslint-disable-next-line no-continue
continue;
}
const posX = (cur - defaultValue) / step * STEP_WIDTH - position + canvasWidth / 2;
ctx.fillRect(posX - 0.5, canvasHeight - 10, 1, 10);
}
for (let cur = begin; cur <= end; cur += step) {
if (cur > max || cur < min) {
// eslint-disable-next-line no-continue
continue;
}
const posX = (cur - defaultValue) / step * STEP_WIDTH - position + canvasWidth / 2;
ctx.fillRect(posX - 0.5, canvasHeight - 20, 1, 20);
}
for (let cur = begin; cur <= end; cur += step) {
if (cur > max || cur < min) {
// eslint-disable-next-line no-continue
continue;
}
const posX = (cur - defaultValue) / step * STEP_WIDTH - position + canvasWidth / 2;
if (magic) {
// magic效果, 原本大小系数为30,改为25,为了解决6位数重叠问题
// eslint-disable-next-line no-restricted-properties
const fontSize = Math.pow((canvasWidth / 2 - Math.abs(posX - canvasWidth / 2)) / (canvasWidth / 2), 2) * 25 + 10;
const dark = parseInt(255 - fontSize * 170 / 40, 10);
ctx.font = ${fontSize}px "PingFang SC"
;
ctx.fillStyle = rgb(${dark}, ${dark}, ${dark})
;
}
ctx.fillText(cur, posX - ctx.measureText(cur).width / 2, canvasHeight - 40);
}
}
if (!this.hasStopped) {
// eslint-disable-next-line no-shadow
doAnimation(e => this.draw(e));
}
}
startRender() {
this.hasStopped = false;
this.draw();
}
endRender() {
this.hasStopped = true;
}
handleScroll(e) {
if (this.prevX) {
const curX = e.nativeEvent.touches[0].clientX;
const delta = curX - this.prevX;
this.prevSpeed = (curX - this.prevX) / (e.nativeEvent.timeStamp - this.prevStamp) * 1000;
this.prevX = curX;
this.prevStamp = e.nativeEvent.timeStamp;
this.moveRuler(delta);
}
}
handleTouchStart(e) {
this.prevX = e.nativeEvent.touches[0].clientX;
this.prevStamp = e.nativeEvent.timeStamp;
// stop decelerate
if (this.decelerateToken) {
cancelAnimation(this.decelerateToken);
}
if (this.dockToken) {
cancelAnimation(this.dockToken);
}
this.startRender();
// eslint-disable-next-line react/destructuring-assignment
const { onRulerTouchStart } = this.props.config;
// eslint-disable-next-line no-unused-expressions
onRulerTouchStart && onRulerTouchStart(e);
}
// eslint-disable-next-line no-unused-vars
handleTouchEnd(e) {
this.prevX = null;
this.decelerate(new Date());
}
decelerate(dt) {
const now = new Date();
if (this.position === this.minPos - STEP_WIDTH || this.position === this.maxPos + STEP_WIDTH) {
this.prevSpeed = 0;
this.dock(now);
return;
}
const sec = (now - dt) / 1000;
const desRate = Math.abs(this.prevSpeed * 2) > acc ? Math.abs(this.prevSpeed * 2) : acc;
const curSpeed = this.prevSpeed > 0 ? this.prevSpeed - sec * desRate : this.prevSpeed + sec * desRate;
if (curSpeed * this.prevSpeed > 0) {
this.moveRuler((this.prevSpeed + curSpeed) * sec / 2);
this.prevSpeed = curSpeed;
this.decelerateToken = doAnimation(() => {
this.decelerate(now);
});
} else {
this.prevSpeed = 0;
this.dockToken = doAnimation(() => {
this.dock(now);
});
}
}
dock(dt) {
let dockPostion = Math.round(this.position / STEP_WIDTH) * STEP_WIDTH;
if (dockPostion < this.minPos) {
dockPostion = this.minPos;
} else if (dockPostion > this.maxPos) {
dockPostion = this.maxPos;
}
const distance = this.position - dockPostion;
const now = new Date();
const sec = (now - dt) / 1000;
const curSpeed = this.prevSpeed + acc / 2 * sec;
const delta = distance > 0 ? (this.prevSpeed + curSpeed) * sec / 2 : -(this.prevSpeed + curSpeed) * sec / 2;
this.dockToken = doAnimation(() => {
if (Math.abs(delta) > Math.abs(distance)) {
this.moveRuler(distance);
this.endRender();
} else {
this.moveRuler(delta);
this.prevSpeed = curSpeed;
this.dock(now);
}
});
}
// eslint-disable-next-line class-methods-use-this
preventScroll(e) {
e.preventDefault();
}
componentDidMount() {
// 默认数据
// config={{
// defaultValue,
// rate, min, max, step,
// }}
// eslint-disable-next-line react/destructuring-assignment
if (this.props.config && this.props.config.defaultValue) {
// eslint-disable-next-line react/destructuring-assignment
this.setState({ amount: this.props.config.defaultValue });
this.draw();
}
// eslint-disable-next-line react/destructuring-assignment
if (this.props.config && this.props.config.motion) {
this.registerMotion();
}
}
// eslint-disable-next-line react/sort-comp
componentDidUpdate(prevProps) {
// eslint-disable-next-line react/destructuring-assignment
if (!prevProps.shown && this.props.shown) {
// add class .p2p-mbp-calc-shown
this.body.classList.add('p2p-mbp-calc-shown');
// eslint-disable-next-line no-unused-expressions
window.Ali && window.Ali.call('setGestureBack', { val: false });
const { onShown } = this.props.config;
// eslint-disable-next-line no-unused-expressions
onShown && onShown();
} else if (prevProps.shown && !this.props.shown) {
// remove class .p2p-mbp-calc-shown
// eslint-disable-next-line react/no-did-update-set-state
this.setState({ hiding: true });
setTimeout(() => {
this.setState({ hiding: false });
}, 300);
this.body.classList.remove('p2p-mbp-calc-shown');
// eslint-disable-next-line no-unused-expressions
window.Ali && window.Ali.call('setGestureBack', { val: true });
}
}
// eslint-disable-next-line react/sort-comp
componentWillUnmount() {
if (this.decelerateToken) {
cancelAnimation(this.decelerateToken);
}
if (this.dockToken) {
cancelAnimation(this.dockToken);
}
}
// eslint-disable-next-line react/sort-comp
constructor(props) {
super(props);
this.body = document.querySelector('body');
const { defaultValue, min, max, step } = props.config;
this.minPos = (-defaultValue + min) / step * STEP_WIDTH;
this.maxPos = STEP_WIDTH * max / step + (-defaultValue / step) * STEP_WIDTH;
}
render() {
// return <div>111</div>;
return (
<div
className={cn('p2p-mbp-calc-container', 'shown')}
ref={el => {
if (el) {
// 为了修复: onTouchMove={e => e.preventDefault()},passive event将失效, https://www.chromestatus.com/features/5093566007214080
el.addEventListener('touchmove', this.preventScroll, { passive: false });
}
}}
>
<div
className="p2p-mbp-calc-rulerWrapper"
onTouchStart={e => this.handleTouchStart(e)}
onTouchMove={e => this.handleScroll(e)}
onTouchEnd={e => this.handleTouchEnd(e)}
>
<canvas
key="canvas"
style={{ height: '100%', width: '100%' }}
ref={el => {
if (el && !this.ctx) {
this.ctx = el.getContext('2d');
const width = 750;
// eslint-disable-next-line no-multi-assign
this.canvasHeight = el.height = width / el.clientWidth * el.clientHeight;
// eslint-disable-next-line no-multi-assign
this.canvasWidth = el.width = width;
}
}}
/>
<div className="p2p-mbp-calc-pointer" />
</div>
</div>
);
}
}
@hd: 2px;
.p2p-mbp-calc-wrapper {
font-size: 16 * @hd;
-webkit-touch-callout: none; /* iOS Safari /
-webkit-user-select: none; / Safari /
-khtml-user-select: none; / Konqueror HTML /
-moz-user-select: none; / Firefox /
-ms-user-select: none; / Internet Explorer/Edge /
user-select: none; / Non-prefixed version, currently
supported by Chrome and Opera */
}
.p2p-mbp-calc-mask {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 997;
transition: background-color 0.2s;
transition-timing-function: linear;
background-color: rgba(0, 0, 0, 0);
&.show {
background-color: rgba(0, 0, 0, 0.4);
}
&.hiding {
background-color: rgba(0, 0, 0, 0);
}
&.hide {
background-color: rgba(0, 0, 0, 0);
height: 0;
}
}
.p2p-mbp-calc-container {
position: fixed;
background: #fff;
bottom: -500 * @hd;
height: 430 * @hd;
left: 0;
width: 100%;
transition: 0.2s;
transition-timing-function: linear;
z-index: 998;
&.keyboard-on {
height: 522 * @hd;
@media (max-height: 1050px) {
// 小屏幕兼容模式
height: 430 * @hd;
.p2p-mbp-calc-rulerWrapper {
display: none;
}
.p2p-mbp-calc-hint {
margin-top: 4 * @hd;
}
.p2p-mbp-calc-p {
margin-top: 10 * @hd;
}
}
.p2p-mbp-calc-disclaimer {
display: none;
}
.p2p-mbp-calc-btn {
bottom: 400px;
}
.p2p-mbp-calc-giftWrapper {
display: none;
}
}
&.shown {
bottom: 0;
}
}
.p2p-mbp-calc-header {
border-bottom: 1PX solid #ddd;
height: 44 * @hd;
font-size: 16 * @hd;
display: flex;
align-items: center;
}
.p2p-mbp-calc-title {
flex: 1;
text-align: center;
}
.p2p-mbp-calc-close{
position: absolute;
top: 7 * @hd;
width: 36 * @hd;
height: 30 * @hd;
.p2p-mbp-calc-closeLeft {
position: absolute;
background: #ddd;
left: 10 * @hd;
top: 13 * @hd;
width: 16 * @hd;
height: 4 * @hd;
-webkit-transform: rotate(45deg);
transform: rotate(45deg);
}
.p2p-mbp-calc-closeRight {
position: absolute;
background: #ddd;
left: 10 * @hd;
top: 13 * @hd;
width: 16 * @hd;
height: 4 * @hd;
-webkit-transform: rotate(135deg);
transform: rotate(135deg);
}
}
.p2p-mbp-calc-amount {
position: relative;
text-align: center;
font-size: 40 * @hd;
margin-top: 13 * @hd;
// override default style
.am-list-item .am-input-control .fake-input-container {
height: 40 * @hd;
line-height: 40 * @hd;
}
.am-list-item .am-input-control .fake-input-container .fake-input {
font-size: 40 * @hd;
text-align: center;
}
.am-list-item .am-input-control .fake-input-container .fake-input.focus:after {
position: static;
}
.am-list-item.am-input-item:after {
border-bottom: none;
}
}
.p2p-mbp-calc-amountInput {
text-align: center;
width: 100%;
border: none;
padding: 0;
}
.p2p-mbp-calc-errorWrapper {
position: absolute;
top: -32 * @hd;
left: 0;
right: 0;
display: flex;
justify-content: center;
}
.p2p-mbp-calc-errorDisplay {
text-align: center;
position: relative;
background-color: #555;
padding: 5 * @hd 9 * @hd;
font-size: 14 * @hd;
color: #fff;
border-radius: 3 * @hd;
&:before {
position: absolute;
content: ' ';
bottom: -5 * @hd;
left: 50%;
border-top: 7 * @hd solid #555;
border-left: 5 * @hd solid transparent;
border-right: 5 * @hd solid transparent;
margin-left: -5 * @hd;
}
}
.p2p-mbp-calc-p {
text-align: center;
margin-top: 17 * @hd;
color: #999;
font-size: 14 * @hd;
}
.p2p-mbp-calc-hint {
text-align: center;
margin-top: 18 * @hd;
color: #999;
font-size: 14 * @hd;
display: flex;
justify-content: center;
align-items: center;
}
.p2p-mbp-calc-hintIcon {
border-radius: 50%;
height: 14 * @hd;
width: 14 * @hd;
margin-top: 1 * @hd;
box-sizing: border-box;
font-size: 12 * @hd;
margin-left: 5 * @hd;
border: 1PX solid #999;
display: flex;
justify-content: center;
align-items: center;
}
.p2p-mbp-calc-profit {
text-align: center;
margin-top: 10 * @hd;
color: #E8541E;
font-size: 28 * @hd;
}
.p2p-mbp-calc-giftWrapper {
display: flex;
padding: 0 8 * @hd;
margin-top: 16 * @hd;
}
.p2p-mbp-calc-gift {
height: 40 * @hd;
text-align: center;
margin: auto;
position: relative;
z-index: 1;
line-height: 40 * @hd;
font-size: 14 * @hd;
padding: 0 20 * @hd;
box-shadow: #E8E8E8 1px 0px 5 * @hd 1px;
border-radius: 23 * @hd;
white-space: nowrap;
overflow: hidden;
.p2p-mbp-calc-giftIcon {
width: 20 * @hd;
height: 20 * @hd;
position: relative;
top: 5 * @hd;
margin-right: 5 * @hd;
}
}
.p2p-mbp-calc-giftArrow {
position: absolute;
z-index: 2;
left: 50%;
margin-left: -4 * @hd;
margin-top: -4 * @hd;
background-color:#fff;
height: 8 * @hd;
width: 8 * @hd;
transform: rotate(45deg);
box-shadow: #eeeeee -@hd -@hd 3 * @hd 0;
}
.p2p-mbp-calc-disclaimer {
padding: 0 15 * @hd;
text-align: center;
margin-top: 20 * @hd;
color: #ccc;
font-size: 12 * @hd;
line-height: 17 * @hd;
}
.p2p-mbp-calc-btn {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 44 * @hd;
color:#fff;
font-size: 17 * @hd;
background-color: #108EE9;
width: 100%;
display: block;
-webkit-appearance: none;
outline: 0 none;
border: 0;
z-index: 997;
transition: 0.2s;
transition-timing-function: linear;
&[disabled] {
background-color: #ddd;
}
}
@-webkit-keyframes cal-fadeIn {
from { background-color: rgba(0, 0, 0, 0); }
to { background-color: rgba(0, 0, 0, 0.4); }
}
@keyframes cal-fadeIn {
from { background-color: rgba(0, 0, 0, 0); }
to { background-color: rgba(0, 0, 0, 0.4); }
}
@-webkit-keyframes cal-fadeOut {
from { background-color: rgba(0, 0, 0, 0.4);}
to { background-color: rgba(0, 0, 0, 0);}
}
@keyframes cal-fadeOut {
from { background-color: rgba(0, 0, 0, 0.4);}
to { background-color: rgba(0, 0, 0, 0);}
}
// the ruler
.p2p-mbp-calc-rulerWrapper {
position: relative;
height: 70 * @hd;
border-bottom: 1PX solid #999999;
}
.p2p-mbp-calc-pointer {
position: absolute;
left: 50%;
bottom: 0;
height: 50 * @hd;
width: @hd;
margin-left: -0.5 * @hd;
background: rgb(54,129,250);
// background-image: url('https://gw.alipayobjects.com/zos/rmsportal/ogEuWZiyhoFZoYEWLnMy.png');
background-position: 0;
background-repeat: no-repeat;
box-shadow: 2px -2px 4px 0 rgba(100,100,100,0.3);
&:before {
content: '';
box-sizing: border-box;
width: 6 * @hd;
border-bottom: 3 * @hd solid rgb(54,129,250);
border-left: 3 * @hd solid transparent;
border-right: 3 * @hd solid transparent;
position: absolute;
top: -3 * @hd;
left: -2.5 * @hd;
height: 3 * @hd;
}
}
.p2p-mbp-calc-shown {
overflow: hidden;
}
// 兼容iphonex
@media only screen
and (device-width: 375px)
and (device-height: 812px)
and (-webkit-device-pixel-ratio: 3) {
.p2p-mbp-calc-container {
@safePadding: 34 * @hd;
// env(safe-area-inset-left)
height: 430 * @hd + @safePadding;
&.keyboard-on {
height: 522 * @hd + @safePadding;
}
.p2p-mbp-calc-btn {
bottom: @safePadding;
}
}
}
// 实际兼容iphonexr(iphonexr的检测分辨率375812
@media only screen
and (device-width: 375px)
and (device-height: 812px)
and (-webkit-device-pixel-ratio: 2) {
.p2p-mbp-calc-container {
@safePadding: 34 * @hd;
// env(safe-area-inset-left)
height: 430 * @hd + @safePadding;
&.keyboard-on {
height: 522 * @hd + @safePadding;
}
.p2p-mbp-calc-btn {
bottom: @safePadding;
}
}
}
// 兼容iphonexr(iphonexr的实际分辨率414896
@media only screen
and (device-width: 414px)
and (device-height: 896px)
and (-webkit-device-pixel-ratio: 2) {
.p2p-mbp-calc-container {
@safePadding: 34 * @hd;
// env(safe-area-inset-left)
height: 430 * @hd + @safePadding;
&.keyboard-on {
height: 522 * @hd + @safePadding;
}
.p2p-mbp-calc-btn {
bottom: @safePadding;
}
}
}
// 兼容iphonexsMax(iphonexsMax的实际分辨率414*896
@media only screen
and (device-width: 414px)
and (device-height: 896px)
and (-webkit-device-pixel-ratio: 3) {
.p2p-mbp-calc-container {
@safePadding: 34 * @hd;
// env(safe-area-inset-left)
height: 430 * @hd + @safePadding;
&.keyboard-on {
height: 522 * @hd + @safePadding;
}
.p2p-mbp-calc-btn {
bottom: @safePadding;
}
}
}