Canvasでピアノロールを作る Canvasでスクロールを扱う際の
座標計算と苦労 / ng-kyoto Angular Meetup #9

Canvasでピアノロールを作る Canvasでスクロールを扱う際の
座標計算と苦労 / ng-kyoto Angular Meetup #9

2019/1/18、ng-kyoto Angular Meetup #9 にて発表した資料です。

2bedb1eb8f841cd3c3ae584600b016e0?s=128

OKUNOKENTARO

January 18, 2019
Tweet

Transcript

  1. $BOWBTͰϐΞϊϩʔϧΛ࡞Δ $BOWBTͰεΫϩʔϧΛѻ͏ࡍͷ
 ࠲ඪܭࢉͱۤ࿑ +BO OHLZPUP"OHVMBS.FFUVQ !PLVOPLFOUBSP

  2. ୭ w Ԟ໺ݡଠ࿠!PLVOPLFOUBSP w ΫϨε΢ΣΞ୅ද w ΞϓϦέʔγϣϯ ɾ ΤϯδχΞ

  3. ࠷ۙ΍ͬͨ͜ͱ w 8FC.VTJDͰ͸Կ͕Ͱ͖Δͷ͔
 '30/5&/%$0/'&3&/$& w ೔ͷ τϥΠΤϥʔ͔Βੜ·Εͨେن໛ઃܭϊ΢ϋ΢
 'SPOUFOE$POGFSFODF'VLVPLB w ࣍ੈ୅8FCΧϯϑΝϨϯε8FC.VTJDηογϣϯ


    ࣍ੈ୅8FCΧϯϑΝϨϯε
  4. ࠓ೔࿩͢͜ͱ w $BOWBTͰϐΞϊϩʔϧΛ࣮૷͢Δ w 3FBDU)PPLTͱ͸Կ͔ w $BOWBTΛѻ͏্Ͱͷۤ࿑

  5. ϐΞϊϩʔϧͱ͸

  6. ϐΞϊϩʔϧ ͍͍ͩͨͲͷԻ੍ָ࡞༻ͷιϑ τ΢ΣΞʹ΋࣮૷͞Ε͍ͯΔ
 ϝϩσΟ Λೖྗ͢ΔͨΊͷΤσΟ λը໘

  7. %FNP

  8. Ͱ͖Δ͜ͱ Ϋ Ϧ οΫͰͷԻූͷೖྗ
 υϥ οάͰͷԻූ௕ͷมߋ
 ϐΞϊϩʔϧදࣔͷ9ํ޲ɺ :ํ޲ͷ֦େॖখɺ εΫϩʔϧ
 ʢσϞͯ͠ͳ͍͚Ͳɺ

    Իූͷίϐϖ΍࡟আ΋౰ͨΓલʹͰ͖Δʣ
  9. ͳʹ͛ʹߴػೳ w Ի੍ָ࡞༻ιϑ τ %"8 ͸ઐ໳ੑ͕ߴ͍ͨΊಛԽͨ͠ػೳ͕ଟ͍ w ςΩε τΤσΟ λʹൺ΂Δͱང͔ʹಛघͳ࣮૷

    w (6*࣮૷Λੜۀͱ͢Δϑϩϯ τΤϯ υ ɾ ΤϯδχΞͱͯ͠
 ͜ΕΛͪΌΜͱ࣮૷Ͱ͖ΔͱεΩϧΞοϓ͢ΔͷͰ͸
  10. ΍ͬͯΈͨ

  11. 3FBDU $BOWBT w OHLZPUP"OHVMBS.FFUVQͰൃදͯ͠Δ͚Ͳ3FBDUΛ࢖ͬͨ w $BOWBTΛ࢖͏͚ͩͳͷͰ7BOJMMB+4Ͱ΋͍͍͕3FBDUΛ࢖ͬͨ w )PPLTͷ࿅श΋͔ͨͬͨ͠ͷͰ3FBDUΛ࢖ͬͨ

  12. 3FBDU)PPLT w ೥݄ɺ 3FBDU$POGʹͯൃද͞Εͨ3FBDUͷ৽͍͠"1*܈ w 4UBCMFͰ͸࢖͏ ͜ͱ͕Ͱ͖ͣnpm i react@next͢Δ͜ͱͰ
 ࣮૷ࡁΈϦ

    ϦʔεΛΠϯε τʔϧͰ͖Δ
  13. 3FBDU)PPLT w ͔ͭͯ3FBDUͷίϯϙʔωϯ τ͸
 class Something extends React.Component
 ͷΑ ͏ʹΫϥεͱ

    ͯ͠هड़͢Δ͔
 SFDPNQPTFΛ࢖ͬͯ)JHIFSPSEFS$PNQPOFOU )0$ ͱͯ͠هड़ͨ͠ w )0$ͱ͸ίϯϙʔωϯ τΛҾ਺ͱ ͯ͠ίϯϙʔωϯ τΛฦؔ͢਺ͷ͜ͱ w ͭ· Γ Ϋϥεͱͯ͠Ͱ͸ͳ͘ɺ ؔ਺ͱ ͯ͠هड़͢Δͱ͍͏ ͜ͱ
  14. 3FBDU)PPLT w SFDPNQPTFͷ࡞ऀ͕'BDFCPPLʹࢀՃͨ͠ w SFDPNQPTF͕ղܾ͠Α ͏ ͱ ͍ͯͨ͠໰୊Λ
 3FBDUຊମͰ׬݁ͯ͠վળͰ͖ΔΑ ͏ʹͳͬͨ

    w ͦ͏΍ͬͯੜ·Εͨͷ͕)PPLT w SFDPNQPTF͸σΟ είϯͱͳͬͨ
  15. ͳͥΫϥεͰ͸μϝ͔ w Ϋϥε͸UIJTʹঢ়ଶΛ࣋ͨͤΔ͜ͱ͕Ͱ͖Δ ʢεςʔ τϑϧɺ ෭࡞༻ʣ  w componentDidMount componentDidUpdateͱ͍ͬͨ


    ϥΠ ϑαΠΫϧϝ ιο υʹΑͬͯUIJT಺͕ॻ͖׵͑ΒΕ֎෦͔Βͷςε τ͕ࠔ೉ w ϥΠ ϑαΠΫϧϝ ιο υʹɺ ίϯϙʔωϯ τͷ ʮ஋Λඳը͢Δʯ ͱ͍͏੹຿Λ௒͑ͨ ෳࡶͳॲཧ͕ॻ͔Ε͕ͪ ʢGFUDIॲཧͱ͔ʣ  w ෳࡶੑ͕૿͢ͱݕূ͕ࠔ೉ʹͳΓόά͕૿͑Δ 3FBDUυΩϡϝϯ τͷϞνϕʔγϣϯͷทΑ Γҙ༁ͯ͠Ҿ༻
  16. 'VODUJPOBM$PNQPOFOU w 3FBDU)PPLTΛ࢖͏ ͜ͱͰίϯϙʔωϯ τΛؔ਺ͱͯ͠هड़ͭͭ͠
 ͞Βʹঢ়ଶΛѻ͏ ͜ͱ͕Ͱ͖ΔΑ ͏ʹͳΔ w 3FBDUIPPLTOPUNBHJD

    KVTUBSSBZT
 https://medium.com/@ryardley/react-hooks-not-magic-just-arrays-cd4f1857236e  w ैདྷͷϥΠ ϑαΠΫϧϝ ιο υ΋useEffect() ͱ͍͏
 3FBDU)PPLTͷҰछΛ࢖͍ɺ ؔ਺ͱ ͯ͠هड़͢Δ
  17. %FNP

  18. ίʔ υͷߏ଄ export default function App() { // ͻͨ͢Β useState()

    const canvasRef = useRef < HTMLCanvasElement > null; useEffect(() => { // ॳظඳը࣌ॲཧ }, []); useEffect(() => { // Իූ௥Ճ࣌ͷॲཧ }, [notes]); useEffect(() => { // εΫϩʔϧҐஔมߋ࣌ͷॲཧ }, [scrollLeft, scrollTop]); const onDragHorizontalScrollBarKnob = useCallback((ev: React.MouseEvent) => { // ਫฏεΫϩʔϧόʔυϥοά࣌ͷϋϯυϦϯά }, [originXY, mediator]); const onDragVerticalScrollBarKnob = useCallback((ev: React.MouseEvent) => { // ਨ௚εΫϩʔϧόʔυϥοά࣌ͷϋϯυϦϯά }, [originXY, mediator]); const onDragScreen = useCallback((ev: React.MouseEvent) => { // Canvas಺υϥοά࣌ͷૢ࡞ʢԻූ௕ͷมߋʣ }, [scrollBarCoord, currentUuid, originXY, notes]); const onMouseDown = useCallback((ev: React.MouseEvent) => { // Canvas಺ͰͷmousedownϋϯυϦϯά }, [ currentUuid, originXY, notes, scrollBarCoord, isDraggingHorizontalScrollBarKnob, isDraggingVerticalScrollBarKnob ]); const onMouseMove = useCallback((ev: React.MouseEvent) => { // canvas಺ͰͷmousemoveϋϯυϦϯά }, [ currentUuid, originXY, notes, isDraggingHorizontalScrollBarKnob, isDraggingVerticalScrollBarKnob ]); return ( <div className="App"> <canvas onMouseDown={ev => onMouseDown(ev)} onMouseMove={ev => onMouseMove(ev)} ref={canvasRef} width="600" height="400" /> </div> ); }
  19. υϥοάϋϯ υ Ϧ ϯάͷ࣮ݱ w Ϛ΢εΫ Ϧ οΫΛͯͦ͠ͷ··཭ͣ͞υϥ οάͱ͍͏࣮૷͕஍ຯʹ೉͍͠ w

    ondrag͸ͦ͏͍࣮ͬͨ૷ͷͨΊͷΠϕϯ τͰ͸ͳ͍ w onmousemoveͰev.button === 1ͷͱ͖ υϥοάͱ൑அ͢ΔΑ ͏ʹ෼ذ
  20. εΫϩʔϧόʔͷϋϯ υ Ϧ ϯά w εΫϩʔϧόʔ͕Ϋ Ϧ οΫ͞Ε͍ͯΔ͔Ͳ͏͔͸ɺ શͯࣗྗͰ࠲ඪܭࢉ
 ͠ͳ͚Ε͹ͳΒͳ͍

    w ϊ ϒͷࠨ্࠲ඪͱӈԼ࠲ඪΛuseState()Ͱ
 ֨ೲ͓͖ͯ͠onmousedownͰ
 ຖճɺ ྖҬ಺͔൑ఆ͢Δ
  21. ԻූͷઃஔͱԻූ௕ͷมߋ w Ϛ΢εͷ࠲ඪ͔Βx - 5, y - 10ͷҐஔΛج఺ʹۣܗ ʢԻූʣ Λඳը

    w Ϛ΢ε࠲ඪΛج఺ʹ͢ΔͱԻූ͕ΧʔιϧͰӅΕͯ͠·͍ඇ௚ײత w ຖճ66*%Λൃߦͯ͠Իූʹ෇༩ w mousedown࣌ʹهԱͨ͠66*%͕mousemove࣌ʹ༗ޮͳΒ͹
 Իූ௕มߋͱ൑ఆ͢Δ w Ϣʔβʹ͸ී௨ʹΫ Ϧ οΫͯ͠ υϥ οάͯ͠Δײ֮
  22. ॳظඳը w 3FBDU)PPLTʹuseRef()͕͋ΔͷͰͦΕΛ࢖ͬͯ$BOWBT3FGΛऔಘ w CanvasRef.currentͰωΠςΟ ϒͳ$BOWBTཁૉͷࢀরΛಘΔ w canvasEl.getContext('2d')ͰίϯςΩε τΛಘ͔ͯΒ͸$BOWBT"1* ௨ΓʹਐΊΕ͹0,

  23. 3FUJOBରԠ w window.devicePixelRatioͰ3FUJOBͳͲͷഒ཰ΛऔಘՄೳ w canvasCtx.scale(dpr, dpr)Ͱഒ཰ઃఆ͕Մೳ w fillRect(x * dpr,

    y * dpr, width * dpr, height * dpr)
 ͱ͔ຖճॻ͔ͳ͘ ͯࡁΉ w ߴEQSදࣔ࣌ʹδϟΪʔ͕ग़ͳ͍Α ͏ʹɺ ຐ๏ͷߦΛ௥Ճ canvasEl.width *= dpr; canvasEl.height *= dpr; canvasEl.style.width = `${canvasEl.width / dpr}px`; canvasEl.style.height = `${canvasEl.height / dpr}px`;
  24. εΫϩʔϧରԠ w Ϋ Ϧ οΫͰͷԻූඳը࣌ʹ
 ͋Β͔͡ΊxʹscrollLeftΛɺ yʹscrollTopΛ଍͢ w εΫϩʔϧόʔͷ υϥ

    οά࣌ʹ࠶ඳըॲཧͰ
 ͢΂ͯͷԻූ࠲ඪ͔ΒscrollLeftͱscrollTopΛҾ͍ͯ࠶ඳը w ͨͿΜݱࡏͷ࣮૷ͷ··Ͱ͸·ͣ͘ ͯ
 ཧ૝తʹ͸Ϋ Ϧ οΫ͞Ε࣮ͨࡍͷ࠲ඪͱϐΞϊϩʔϧશମ͔ΒΈͨԾ૝࠲ඪΛ
 ܥ౷؅ཧ͠ͳ͍ͱɺ ॎԣ֦େॖখͳͲͰࢮ͵
  25. ۤ࿑ͨ͠఺ ʢ3FBDUฤʣ w useEffect()͕૿͑Δ ๲ΒΉ w Πϕϯ τϋϯ υ Ϧ

    ϯάʹԠͨ͡࠶ඳըͷछྨ͕ଟ͍͍ͤ΋͋Δ͚Ͳ
 ෳ਺ͷuseEffect()ʹΑͬͯίϯϙʔωϯ τؔ਺͕ංେԽͨ͠ w Իූυϥ οά࣌ͱεΫϩʔϧόʔ ɾ υϥ οά࣌Ͱ࠶ඳըॲཧ͕ҧ͏ w useEffect(cb, [])ͷୈೋҾ਺ʹࢀরΛྻڍ͢Δ͜ͱͰ
 ͦͷࢀর͕มԽͨ͠ͱ͖ͷΈuseEffectΛ࣮ߦ͢Δ͜ͱ͕Ͱ͖Δ
 ʢuseEffect(() => {}, [scrollLeft]) ͳͲʣ
  26. ۤ࿑ͨ͠఺ ʢ3FBDUฤʣ w 3FBDUͷϚ΢εૢ࡞࣌ͷϋϯ υ Ϧ ϯάҾ਺͸MouseEventܕͰ͸ͳ͍ w 3FBDUख़࿅ऀʹ͸౰ͨΓલͷ࿩͔΋ w

    SyntheticEventͱ͍͏ܗࣜͰ౉ΔͷͰnativeEventऔಘͷͨΊʹ͸
 ev.persist();
 const nativeEv = ev.nativeEvent;
 ͷΑ ͏ʹҰ୴persist͢Δඞཁ͕͋Δ w ͜͏ ͠ͳ͍ͱoffsetX offsetY͕औΕͳ͍ͷͰ஫ҙ
  27. ۤ࿑ͨ͠఺ ʢ$BOWBTฤʣ w ͻͨ͢Β࠲ඪܭࢉͱͷઓ͍ͱͳΔ w fillRect()ͳͲ͢΂ͯͷඳըॲཧ͸ޙউͪͳͷͰίʔϧॱͰ݁Ռ͕มΘΔ w ։ൃத͸Իූ͕εΫϩʔϧόʔΛಥ͖ൈ͚Δͱ͔βϥ w ϨΠϠʔͳΜͯ֓೦͸ແ͍

    ʢؤு࣮ͬͯ૷͢Δ͔$BOWBTϥούʔϥΠ ϒϥ ϦʹཔΔʣ  w ͻͨ͢ΒԚ͍ίʔ υͱ ϒϥ΢β্ͷखಈσόοάΛ܁Γฦͯ͠
 ͧ͜͜ͱ͍͏ ͱ͜ΖͰϦ ϑΝΫλ Ϧ ϯά
  28. ۤ࿑ͨ͠఺ ʢ$BOWBTฤʣ w 3FBDUͷίϯϙʔωϯ τؔ਺͔Β$BOWBTʹؔ͢Δॲཧ͸͢΂ͯ௥͍ग़ͨ͠ w CanvasMediatorͱ͍͏தؒҕৡ༻ͷγϯάϧ τϯΛ࡞ͬͯ
 ඳըܥͱ࠲ඪऔಘܥ͸͢΂ͯͦͬͪʹ࣮૷͢ΔΑ ͏

    Ϧ ϑΝΫλ Ϧ ϯά w Իූ͚ͩͳΒ؆୯ͩͬͨ w εΫϩʔϧόʔͷ࣮૷Λ࢝ΊͨลΓ͔Βࠞಱͱ ࢝͠Ίͯ
 ϒϥ΢βͷ΢Πϯ υ΢࣮૷ͷ࢓༷ͱ͔ړΓ࢝Ίͨ w 04ͷ΢Πϯ υ΢γεςϜΛ࣮૷ͨ͠ਓ͸ϚδͰҒେͩͱࢥ͏
  29. ࠓޙ΍͍͖͍ͬͯͨػೳ w Ϋ Ϧ οΫͯ͠ஔ͍ͨԻූͷҠಈ ɾ ࡟আ w Ͳͷۣܗ͕Ϋ Ϧ

    οΫ͞Ε͔͕ͨऔΕͦ͏ʹͳ͍ͷͰ
 εΫϩʔϧόʔͱಉ͡Ͱɺ ͢΂ͯྖҬܭࢉͰදݱ͢Δ͔͠ͳ͍͔΋ wυϥοάͨ͠··$BOWBT֎ʹϚ΢εΛಈ͔ͨ͠ΒࣗಈͰεΫϩʔϧ w ࠲ඪ͸औΕΔͬΆ͍ɺ ࠶ඳըܭࢉ͕΍ͨΒ໘౗ͬΆ͍ w ࣮ࡍʹ࠶ੜϘλϯΛ෇͚ͯԻΛ໐Β͢ w ເͷ·ͨເ
  30. ͜͜·Ͱۤ࿑ͯ͠ࢥͬͨ͜ͱ w "OHVMBSͰେن໛ΞϓϦΛ࡞Εͯ΋$BOWBTશવ࡞Εͳ͍ w 3FBDU͡Όͳ͘ ͍͍ͯ w $VTUPN&MFNFOUTͱ૬ੑΑͦ͞͏

  31. 5IBOLZPV %0.ʹײँ͠ͳ͕Β$BOWBTͰ(6*ͷԞਂ͞ʹ৮ΕΑ ͏ ʂ