首页 >web前端 >js教程 >[ 随机软件项目:dev.to Frontend 结论

[ 随机软件项目:dev.to Frontend 结论

Susan Sarandon
Susan Sarandon原创
2024-10-10 06:17:02582浏览

끝났어!

한때 dev.to 프론트엔드 챌린지였던 3부작의 마지막 에피소드에 도달했습니다. 우리 꽤 주의가 산만해졌죠, 그렇죠!? 챌린지 자체를 제출하기에는 너무 늦었지만, 우리는 그 과정에서 몇 가지 좋은 교훈을 얻었으며 깔끔하고 작은 라이브러리도 게시했습니다. 하지만 이제 마무리할 시간입니다.

오늘은 주로 두 가지 중요한 사항에 중점을 둘 것입니다. 태양 행성 라이브러리를 통합하여 행성의 실제 3D 위치를 실시간으로 계산하는 것과 개별 행성에 대한 클릭 이벤트를 통해 원래 챌린지의 마크업 통합을 마무리하는 것입니다. 메시.

태양 행성 통합

2부에서 제가 자주 읽는 천체 교과서 중 하나에 있는 유용한 알고리즘을 기반으로 괜찮은 작은 태양 행성 라이브러리를 구성했다는 것을 기억하실 것입니다. 이후 우리는 쉽게 액세스할 수 있도록 기본 원사 게시를 사용하여 npmjs.org에 게시했습니다. 이제 자체 프로젝트 페이지도 생겼습니다! 멋지네요.

[ Random Software Project: dev.to Frontend Conclusion

기본 원사로 시작하여 태양계를 추가한 다음 1부에서 중단한 부분부터 시작할 수 있습니다. 그런 다음, Yarn run dev를 호출하여 앱의 라이브 버전을 실행할 수 있습니다. 이제 사용할 준비가 되었습니다!

행성 위치 계산

라이브러리를 가져온 후에는 각 행성에 대한 함수 호출에 전달할 행성 궤도 테이블의 데이터(또는 "카탈로그")도 필요합니다. 우리는 이것을 출판된 원본 논문의 이름을 따서 "스탠디시 카탈로그"라고 불렀습니다. 이를 복사하여 앱의 리소스로 로드한 다음(지금은 원시 가져오기만 해도 됨) 애플리케이션 로드 시 구문 분석합니다.

이제 "이름" 매개변수를 포함하도록 "행성 팩토리"(buildPlanet())를 업데이트할 수 있습니다(따라서 이 카탈로그와 상호 연관시킬 수 있음). 그런 다음 일치하는 항목이 발견되면 라이브러리의 getRvFromElementsDatetime() 메서드를 사용하여 해당 메시에 할당할 위치를 결정합니다.

const poe = window.app.catalog(hasOwnProperty(name.toLowerCase())
    ? window.app.catalog[name.toLowerCase()]
    : null;
if (pow) {
    const [r, _] = solarplanets.getRvFromElementsDatetime(poe, window.app.now);
    mesh.position.set(
        spatialscale * r[0],
        spatialscale * r[1],
        spatialscale * r[2]
    );
}

이 window.app.now Date 개체도 추가할 예정이지만, 보다 응집력 있는 시간 모델링에 사용할 수 있는 새로운 THREE.Clock 기능도 있습니다. 그리고 해당 위치를 할당하고 나면(물론 창 로드 시 카탈로그 JSON을 구문 분석하는 것 외에도 새 이름 매개변수도 포함하도록 이 팩토리에 대한 호출을 업데이트) 이제 행성 위치를 볼 수 있습니다!

[ Random Software Project: dev.to Frontend Conclusion

샘플링 궤도 추적

한 가지 주목할 만한 점은 현재 행성의 "실제" 위치는 있지만 우리가 생성한 선의 "궤적"과 일치하지 않는다는 것입니다. 그것들은 여전히 ​​"평평한" 원으로 구현되므로 정렬되지 않는 것은 놀라운 일이 아닙니다. 지금 바로 수정해 보겠습니다.

행성의 궤도 추적을 계산하기 위해 비슷한 함수 호출을 사용하지만 "지금"부터 미래의 한 궤도 주기까지 시간에 따라 샘플링합니다. 하지만 이 공전 주기는 행성마다 다르기 때문에 카탈로그를 다시 살펴봐야 합니다. 궤도 추적 "공장"에도 "이름" 매개변수를 추가한 다음 해당 포인트가 계산되는 방식을 자세히 살펴보고 업데이트해 보겠습니다.

먼저 '균일한' 샘플링을 시간에 따른 샘플링으로 대체하겠습니다. 우리는 여전히 고정된 수의 포인트를 사용하지만 "지금" 시간부터 미래의 한 궤도 주기까지 포인트를 보간할 것입니다. 카탈로그 모델 항목에서 이 "궤도 주기"를 계산하는 도우미 메서드를 추가하겠습니다. 이는 이전 기사에서 실제로 살펴본 매우 간단한 방정식입니다.

function getPeriodFromPoe(poe) {
    const MU_SUN_K3PS2 = 1.327e11;
    const au2km = 1.49557871e8;
    return 2 * Math.PI * Math.sqrt(Math.pow(poe.a_au * au2km, 3.0) / MU_SUN_K3PS2);
}

이제 이를 사용하여 buildOrbitTrace() 함수의 for 루프를 변경하겠습니다.

const poe = window.app.catalog.hasOwnProperty(name.toLowerCase())
    ? window.app.catalog[name.toLowerCase()]
    : null;
if (pow) {
    const T0 = window.app.now;
    const T_s = getPeriodFromPoe(poe);
    for (var i = 0; i < (n + 1); i += 1) {
        const t_dt = new Date(T0.valueOf() + T_s * 1e3 * i / n);
        const [r, _] = solarplanets.getRvFromElementsDatetime(poe, t_dt);
        points.push(new THREE.Vector3(
            spatialScale * r[0],
            spatialScale * r[1],
            spatialScale * r[2]
        ));
    }
}

이제 3D 궤도 추적을 볼 수 있습니다! 그것들은 약간 기울어져 있고 약간 기울어져 있으며 우리 행성의 위치와 일직선을 이루고 있습니다. 정말 멋지네요.

[ Random Software Project: dev.to Frontend Conclusion

Raycaster 충돌 감지 마무리

1부를 마쳤을 때 장면의 메시와의 충돌을 (각 프레임에서) 평가하기 위해 레이캐스터를 방금 설치했습니다. 마우스 위치가 올바르게 변환되지 않았다는 간단한 업데이트가 하나 있습니다. y 구성요소 계산에 부호 오류가 있었는데, 대신 다음과 같이 표시되어야 합니다.

window.app.mouse_pos.y = -(event.clientY / window.innerHeight) * 2 + 1;

이 문제를 해결한 후 이러한 충돌이 실제로 발생하는지 디버깅/검증해 보겠습니다. 교차점이 계산되면(animate() 함수에서) 해당 교차점을 처리기 processCollision()에 전달합니다. 또한 트리거된 교차점 수를 계산하여 추가할 마우스 오버 효과를 언제 "재설정"해야 하는지 알 수 있습니다.

let nTriggered = 0;
if (intersections.length > 0) {
    intersections.forEach(intersection => {
        nTriggered += processCollision(intersection) ? 1 : 0;
    });
}
if (nTriggered === 0) {
    // reset stuff
}

With that in mind, we're ready to write our handler. First, let's just check to make sure the mesh has a name. (Which will require, by the way, that we assign the planet name in the buildPlanet() factory to the mesh that is created. In this function, we will also need to set a scale for the mesh, like mesh.scale.set(2, 2, 2), to make collisions easier. Make sure you make these changes now.) If it has a name that matches a known planet, we'll log it to the console for now.

function processCollision(intersection) {
    const name = intersection.object.name;
    const names = planets.map(p => p.name.toLowerCase());
    if (name !== "" && names.includes(name)) {
        console.log(name);
        return true;
    }
    return false;
}

(Note we are normalizing names to lower case for comparison--always a good idea, just in case you lose track of consistent capitalization within the model data you are using.)

This should be enough for you to see (for example) "venus" should up in your console log when you move the mouse over Venus.

Trigger Mouseover Effects

With intersections being computed correctly, let's add some effects. Even if the user doesn't click, we want to indicate (as a helpful hint) that the mouse is over something clickable--even though it's a mesh in a 3d scene, and not just a big button in a 2d UI. We're going to make two changes: the mesh will turn white, and the cursor will change to a "pointer" (the hand, instead of the default arrow).

In our processCollision() function, we'll replace the console.log() call with a change to the object material color, and change the body style of the document (a little cheating here...) to the appropriate style.

//mouseover effects: change planet color, mouse cursor
intersection.object.material.color.set(0xffffff);
window.document.body.style.pointer = "cursor";
return true;

We'll also need to use our collision check aggregation (nTriggered, in the animate() function) to reset these changes when the mouse is not hovering over a planet. We had an if block for this, let's populate it now:

if (nTriggered === 0) {
    // reset state
    window.document.body.style.cursor = "inherit";
    planets.forEach(p => {
        const sgn = window.app.scene.getObjectByName(p.name.toLowerCase());
        if (sgn) {
            sgn.material.color.set(p.approximateColor_hex);
        }
    });
}

Give it a spin! Hover over some planets and check out that deliciousness.

Trigger On-Click Dialog

To satisfy the original intention (and requirements) of the challenge, we need to integrate the markup. We'll move all of this markup into our HTML, but tweak the style to hide it so the 3d canvas continues to take up our screen. Instead, when a planet is clicked, we'll query this markup to display the correct content in a quasi-dialog.

If you copy the entire body of the markup, you can tweak our top-level CSS to make sure it doesn't show up before a mouse button is pressed:

header, section, article, footer {
    display: none
}

And from the console, you can verify that we can use query selector to grab a specific part of this markup:

window.document.body.querySelector("article.planet.neptune");

So, we're ready to implement our on-click event. But, we need to keep track of the mouse state--specifically, if the button is down. We'll add an is_mouse_down property to our module-scope window.app Object, and add some global handlers to our onWindowLoad() function that update it:

window.addEventListener("mousedown", onMouseDown);
window.addEventListener("mouseup", onMouseUp);

The sole purpose of these event handlers is to update that window.app.is_mouse_down property so we'll be able to check it in our collision intersection handler. So, they're pretty simple:

function onMouseDown(event) {
    window.app.is_mouse_down = true;
}

function onMouseUp(event) {
    window.app.is_mouse_down = false;
}

In a more formally designed application, we might encapsulate management (and lookup) of mouse state (and signals) in their own object. But this will work for our purposes. Now, we're ready to revisit the body of our processCollision() handler and add support for showing the dialog:

if (window.app.is_mouse_down) {
    dialogOpen(name);
}

We'll add two handlers for dialog management: dialogOpen() and dialogClose(). The latter will be invoked when the mouse moves out of the mesh collision--in other words, exactly where we reset the other UI states, in animate():

if (nTriggered === 0) {
    // ...
    dialogClose();
}

Now we're ready to write these. Since we're not using any kind of UI library or framework, we'll manually hand-jam a basic

that we populate from the article content, then style as needed.

function dialogOpen(name) {
    let dialog = window.document.body.querySelector(".Dialog");
    if (!dialog) {
        dialog = window.document.createElement("div");
        dialog.classList.add("Dialog");
        window.document.body.appendChild(dialog);
    }

    // query article content to populate dialog
    const article = window.document.body.querySelector(`article.planet.${name}`);
    if (!article) { return; }
    dialog.innerHTML = article.innerHTML;
    dialog.style.display = "block";
}

A word of caution: Assigning innerHTML is a surprisingly expensive operation, even if you're just emptying it with a blank string! There's all sorts of parsing and deserialization the browser has to do under the hood, even if it is a method native to the DOM API itself. Be careful.

Conversly, the dialogClose() event is quite simple, since we just need to check to see if the dialog exists, and if it does, close it. We don't even need to know the name of the relevant planet.

function dialogClose() {
    const dialog = window.document.body.querySelector(".Dialog");
    if (!dialog) { return; }
    dialog.style.display = "none";
}

And of course we'll want to add some modest styling for this dialog in our top-level CSS file for the application:

.Dialog {
    position: absolute;
    top: 0;
    left: 0;
    background-color: rgba(50, 50, 50, 0.5);
    color: #eee;
    margin: 1rem;
    padding: 1rem;
    font-family: sans-serif;
    border: 1px solid #777;
    border-radius: 1rem;
}

Wrapping It Up

Of course, we still only have the "inner" planets. We can easily add the "outer" planets just by augmenting our module-scope planets dictionary that is used to populate the scene. (Entries in the markup and the planet catalog already exist.)

// ... }, 
{
    "name": "Jupiter",
    "radius_km": 6.9911e4,
    "semiMajorAxis_km": 7.78479e8,
    "orbitalPeriod_days": 4332.59,
    "approximateColor_hex": 0xaa7722
}, {
    "name": "Saturn",
    "radius_km": 5.8232e4,
    "semiMajorAxis_km": 1.43353e9,
    "orbitalPeriod_days": 10755.7,
    "approximateColor_hex": 0xccaa55
}, {
    "name": "Uranus",
    "radius_km": 2.5362e4,
    "semiMajorAxis_km": 2.870972e9,
    "orbitalPeriod_days": 30688.5,
    "approximateColor_hex": 0x7777ff
}, {
    "name": "Neptune",
    "radius_km": 2.4622e4,
    "semiMajorAxis_km": 4.50e9,
    "orbitalPeriod_days": 60195,
    "approximateColor_hex": 0x4422aa
}

This is even enough for you to start doing some verification & validation when you walk outside tonight! Take a close look at how the planets are arranged. At midnight, you're on the far side of the earth from the sun. What planets should you be able to see, if you trace the path of the zodiac (or ecliptic) from east to west? Can you see Venus, and when? What's the relative arrangement of Jupiter and Saturn? You can predict it!

[ Random Software Project: dev.to Frontend Conclusion

Cool stuff.

以上是[ 随机软件项目:dev.to Frontend 结论的详细内容。更多信息请关注PHP中文网其他相关文章!

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn