Я работаю над компонентом React/TypeScript, который отображает фазы луны на основе значения от 0 до 1. Проблема в том, что фазы луны не отображаются правильно между фазами первой четверти и последней четверти. В частности, компонент показывает растущую луну, хотя она должна быть убывающей, и наоборот.
Я попробовал использовать Canvas вместо SVG, но это не решило проблему. Я также попытался изменить расчет видимой части Луны так:
const visiblePortion = Math.cos(normalizedPhase * Math.PI * 2);
Но проблема не исчезла. Вместо того, чтобы правильно отображать луну в диапазоне от 0,25 до 0,75, светлые/темные части луны по-прежнему меняются местами для этих фаз.
Фаза луны представлена числом от 0 до 1, где:
Я хочу, чтобы луна постепенно увеличивалась между 0 и 0,5, а затем убывала между 0,5 и 1, но для фаз между 0,25 и 0,75 светлые и темные части меняются местами.
Вот код, с которым я работаю:
import React from "react";
interface MoonPhaseProps {
phase: number;
moonTexture?: string;
rotation?: number;
}
const MoonPhase = ({
phase,
moonTexture = "/image/moon/moon.svg",
rotation = 0,
}: MoonPhaseProps) => {
// Ensure phase is between 0 and 1
const normalizedPhase = Math.max(0, Math.min(phase, 1));
console.info(phase, normalizedPhase);
// Convert phase to radians
const phaseRadians = normalizedPhase * 2 * Math.PI;
// Determine if it's a waxing or waning phase
const isWaxing = normalizedPhase <= 0.5;
// Calculate the visible portion of the moon
const visiblePortion = Math.cos(phaseRadians);
// Adjust the path for waxing and waning phases
const pathD = isWaxing
? `M50,0 A50,50 0 1,1 50,100 A${
50 * Math.abs(visiblePortion)
},50 0 1,0 50,0`
: `M50,0 A50,50 0 1,0 50,100 A${
50 * Math.abs(visiblePortion)
},50 0 1,1 50,0`;
return (
<svg
width = "100%"
viewBox = "0 0 100 100"
data-phase = {phase}
style = {{
transform: `rotate(${rotation}deg)`,
transition: "transform 0.3s ease-in-out",
}}
>
<defs>
<pattern
id = "moonTexturePattern"
patternUnits = "userSpaceOnUse"
width = "100"
height = "100"
>
<image href = {moonTexture} x = "0" y = "0" width = "100" height = "100" />
</pattern>
<mask id = "moonMask">
<rect x = "0" y = "0" width = "100" height = "100" fill = "white" />
<path d = {pathD} fill = "black" />
</mask>
</defs>
{/* Moon texture */}
<circle cx = "50" cy = "50" r = "49" fill = "url(#moonTexturePattern)" />
{/* Shading for the dark side of the moon */}
<circle
cx = "50"
cy = "50"
r = "49"
fill = "rgba(0,0,0,0.7)"
mask = "url(#moonMask)"
/>
</svg>
);
};
export default React.memo(MoonPhase);
Вот лист (или хотя бы ссылка на него), показывающий фазы Луны и их значения: Moon_phase_explainer
Заранее спасибо.
Вы хотите, чтобы видимая часть Луны изменялась от 0 до 100% в соответствии с правилом cos (фазы), но вы не смогли проверить, что ваши конечные точки при преобразовании угла имеют смысл. Кажется, вы хотите:
соз(0) = 1, соз(пи/2) = 0, соз(пи) = -1
Но у вас есть строка, устанавливающая фазовый угол в радианах как умноженный на 2*pi:
const visiblePortion = Math.cos(normalizedPhase * Math.PI * 2);
Это должно читаться
const visiblePortion = Math.cos(normalizedPhase * Math.PI);
Где знак видимой части определяет, какую сторону скрывать.
И тогда должно быть либо именно то, что вы хотите, либо наоборот (в этом случае s/cos/sin/).
Спасибо за ответ, но это не работает. Луна теперь освещена наполовину на уровне 0,5; он должен быть наполовину освещен с одной стороны на 0,25, а с другой на 0,75, а не 0,5.
Я пропустил путь и «построил» маску из эллипса. Я не знаю, сможете ли вы это использовать. В любом случае, возможно, другие смогут.
document.forms.moon.phase.addEventListener('input', e => {
let e1 = document.getElementById('e1');
let r1 = document.getElementById('r1');
let r2 = document.getElementById('r2');
let val = e.target.value;
if (val <= .25) {
r1.setAttribute('fill', '#444');
r2.setAttribute('fill', 'white');
e1.setAttribute('fill', '#444');
e1.setAttribute('rx', 50 - val * 200);
} else if (val <= .50) {
r1.setAttribute('fill', '#444');
r2.setAttribute('fill', 'white');
e1.setAttribute('fill', 'white');
e1.setAttribute('rx', (val-.25) * 200);
} else if (val <= .75) {
r1.setAttribute('fill', 'white');
r2.setAttribute('fill', '#444');
e1.setAttribute('fill', 'white');
e1.setAttribute('rx', 50 - (val-.50) * 200);
} else if (val <= 1) {
r1.setAttribute('fill', 'white');
r2.setAttribute('fill', '#444');
e1.setAttribute('fill', '#444');
e1.setAttribute('rx', (val-.75) * 200);
}
});
<form name = "moon">
<input name = "phase" type = "range" min = "0" max = "1" value = "0" step = ".01">
</form>
<svg width = "200" viewBox = "0 0 100 100">
<defs>
<pattern id = "moonTexturePattern" patternUnits = "userSpaceOnUse"
width = "100" height = "100">
<image href = "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e1/FullMoon2010.jpg/253px-FullMoon2010.jpg" x = "-10" y = "-10" width = "120" height = "120" />
</pattern>
<mask id = "moonMask">
<rect id = "r1" x = "0" y = "0" width = "50" height = "100" fill = "#444" />
<rect id = "r2" x = "50" y = "0" width = "50" height = "100" fill = "#444" />
<ellipse id = "e1" cx = "50" cy = "50" rx = "0" ry = "50" fill = "black"/>
</mask>
</defs>
<circle cx = "50" cy = "50" r = "49" fill = "url(#moonTexturePattern)"
mask = "url(#moonMask)" />
</svg>
<p><a href = "https://en.wikipedia.org/wiki/File:FullMoon2010.jpg">Moon image from wikimedia.org</a></p>
Все в порядке, но я сделал маску, потому что по умолчанию фоном должна быть луна. В вашем примере часть луны не видна (например на 0.25 левая половина - это ничего, а правая половина - это луна. Я хочу, чтобы было там, где левая половина - это луна, а правая половина — это затемненная луна.) Другого способа сделать это без маски я не нашла.
@BestCodes Я обновил свой ответ: заменил все черные заливки в маске серым цветом. Это больше похоже на это?
Большое спасибо за все ваши усилия! Это намного лучше, хотя и не идеально. Вместо светлого наложения у меня темное. Вот как выглядит моя луна на 0,2 (на данный момент она все еще работает правильно): i.imgur.com/z90AGCN.png
Я думаю, что могу просто изменить цвет белой области. Спасибо!
Итак, похоже, что маска просто меняет прозрачность изображения луны, а это означает, что «маска» будет того же цвета, что и фон, на котором используется луна. Чтобы сделать его черным, нам нужно встроить в SVG черный фон. Просто добавьте еще один круг в SVG перед другим: <circle cx = "50" cy = "50" r = "49" fill = "black" /> Спасибо за вашу помощь!
У вас есть рабочая ссылка ?