NativeScript-Vueでの行間の設定はLabel>Spanを使うべき

8 min read読了の目安(約7600字

NativeScriptではCSSは完全にサポートされていない

NativeScriptはCSSを完全にサポートしておらず、いくつか使えないプロパティがある。最新のサポートされたプロパティは公式の一覧を参照すればわかる。今回はその中でも挙動がわかりにくい行間の設定(line-height)について取り上げる。

NativeScriptではテキストの行間をCSSと同様にline-heightで設定できるが、挙動はWebでのCSSのそれとは大きく異なる。

Webでの行間の設定の仕方

CSSでfont-size: 16pxline-height: 1とすれば行と行の間の空白は0pxになり、line-height: 1.5とすればfont-size * (line-height - 1) = 8pxが空白になる。line-height: 200%とかline-height: 2emとすればline-height: 2と同義になる。またline-height: 0.8などとすればギチギチに行間を詰めることができる。

NativeScriptでの行間の設定の仕方

(私はNativeScript-Vueを使っているので他のNativeScriptでの挙動について調べてはいないが、NativeScript-Vueのドキュメントではline-heightについては一言も触れられていないため同じなのではないかと憶測する)

NativeScriptでは、複数行にわたる文章では<Label>を使う。<Label>コンポーネント全体の文字色を変更する場合、<Label class="red" text="hello" textWrap="true" />とする。もしそのウィジェットの一部のみを赤字にしたい場合、

<Label>
	<FormattedString>
		<Span class="red" text="this text will be red." />
	</FormattedString>
</Label>

などとすればよい、と公式のFormatted Stringについてのドキュメントに書いてある。ではその調子でline-heightを変えてみよう!line-height: 1.5にすればまぁちょうどよくなるやろ。

line-height: 1.5ではダメ

tl;dr: line-height: 1.5;と同等の効果を得たい場合、font-size: 16pxであればline-height: 8;とする。ただし<Label>のみの場合。

<template>
  <Page>
    <ScrollView>
      <StackLayout>
        <Label :text="text" class="a" textWrap="true" />
      </StackLayout>
    </ScrollView>
  </Page>
</template>

<script>
export default {
  data() {
    return {
      text:
        'Hurricane Dorian was the strongest hurricane to affect the Bahamas, causing catastrophic damage in September 2019.',
    }
  },
}
</script>

<style lang="scss" scoped>
.a {
  background-color: #eee;
  font-size: 16;
  line-height: 1.5;
  margin: 16 0;
}
</style>

...アレ?なんかおかしい。種明かしをすると、NativeScriptでのline-heightは、(<Label>に適用された場合(※ここがあとで重要になる))行と行の間の空白をdip(device independent pixels, NativeScriptで基本となる単位と理解している)で表したものであって、Webにおけるline-heightのような倍率ではない。line-height: 1.5emとしてみたり、font-size: 16pxと単位を明記してみたりしても無駄である。ちなみにline-height: 150%などとすると更に謎の行間になる。なので、Webにおけるline-height: xxxと同等のものを設定したい場合、font-size(xxx - 1)を掛けた値にする必要がある。

<style lang="scss" scoped>
.a {
  background-color: #eee;
  font-size: 16;
  line-height: 8;
  margin: 16 0;
}
</style>

行間を詰めることはできない(私の知る限り)

WebのCSSであればline-height: 0.8;などとすることで詰められる。長い文章ではあまり多用することはないが、見出しやタイトルなどで使うこともあるだろう。しかしNativeScriptでは↑の仕様のうえ、負の値をline-heightに設定してもline-height: 0と同等に振る舞うため、行間を詰めることはできない(多分)。NativeScriptのSlackでも質問したが解決しなかった:

LabelのなかにSpanを入れると挙動が変わる

この記事の主題。<Label>のなかに<Span>を入れるとline-heightの挙動が変わる。以下ではclass-aというクラスにfont-size: 16;かつline-height: 24;というスタイルをあてているので、LabelだけであればWebでのCSS換算でline-height: 2.5のとても大きな行間となる(上)。しかしそのなかに<Span>タグを入れると行間はWebでのCSS換算でline-height: 1.5のわりとちょうどよい行間となる(中)。そしてSpanタグのほうに同じクラスをあてるとline-heightプロパティは無視される(下)。

<template>
  <Page>
    <ScrollView>
      <StackLayout>
        <Label
          class="class-a"
          :text="'Label.class-a\n' + text"
          textWrap="true"
        />
        <Label textWrap="true" class="class-a">
          <Span class="section" text="Label.class-a>Span" />
          <Span :text="'\n' + text" />
        </Label>
        <Label textWrap="true">
          <Span class="section" text="Label>Span.class-a" />
          <Span class="class-a" :text="'\n' + text" />
        </Label>
      </StackLayout>
    </ScrollView>
  </Page>
</template>

<script>
export default {
  data() {
    return {
      text:
        'Hurricane Dorian was the strongest hurricane to affect the Bahamas, causing catastrophic damage in September 2019.',
    }
  },
}
</script>

<style lang="scss" scoped>
.class-a {
  margin: 16 0;
  background-color: #eee;
  font-size: 16;
  line-height: 24;
}

.section {
  color: #a8d9f5;
  background-color: #180630;
}
</style>

つまり、どうもLabelコンポーネントにおいては、行間はfont-size * (line-height)で計算されるが、Labelコンポーネント内にSpanコンポーネントがある場合においては、行間はfont-size * (line-height - 1)で計算されるうえに、Spanコンポーネントにline-heightのプロパティを指定しても、反映されないらしいのだ。厄介すぎる。さらに、<FormattedString>は(少なくともNS-Vueにおいては)なしでも同じ動作をする。このややこしさを一覧にしてみたので、各自コピペして数値や包含関係をいじってみてほしい:

<template>
  <Page>
    <ScrollView>
      <StackLayout>
        <Label :text="'Label\n' + text" textWrap="true" />
        <Label class="class-a" :text="'Label.class-a\n' + text" textWrap="true" />
        <Label textWrap="true">
          <Span class="section" text="Label>Span" />
          <Span :text="'\n' + text" />
        </Label>
        <Label textWrap="true">
          <Span class="section" text="Label>Span.class-a" />
          <Span class="class-a" :text="'\n' + text" />
        </Label>
        <Label textWrap="true" class="class-a">
          <Span class="section" text="Label.class-a>Span" />
          <Span  :text="'\n' + text" />
        </Label>
        
        <Label textWrap="true">
          <Span class="section" text="Label>Span.class-a" />
          <Span class="class-a" :text="'\n' + text" />
        </Label>
        <Label class="class-a" textWrap="true">
          <Span class="section" text="Label.class-a>Span.class-a" />
          <Span class="class-a" :text="' \n' + text" />
        </Label>
        <Label class="class-a" textWrap="true">
          <FormattedString>
          <Span class="section" text="Label.class-a>FormattedString>Span" />
          <Span :text="'\n' + text" />
          </FormattedString>
        </Label>   
        <Label textWrap="true">
          <FormattedString class="class-a" >
            <Span class="section" text="Label>FormattedString.class-a>Span" />
            <Span  :text="'\n' + text" />
          </FormattedString>
        </Label>   
        <Label textWrap="true">
          <FormattedString>
            <Span class="section" text="Label>FormattedString>Span.class-a" />
          <Span  class="class-a" :text="' \n' + text" />
          </FormattedString>
        </Label>   
        </StackLayout>
      </ScrollView>
    </Page>
</template>

<script >
  export default {
    data() {
      return {
        text: "Hurricane Dorian was the strongest hurricane to affect the Bahamas, causing catastrophic damage in September 2019."
      }
    }
  }
</script>

<style lang="scss" scoped>
.class-a {
  margin: 16 0;
  background-color: #eee;
  font-size: 16;
  line-height: 24;
}
.section {
  color: #a8d9f5;
  background-color: #180630;
}
</style>


どうすればいいのか

わからない。私は今の時点では次のようにしようと思っている。

複数行に渡らない、行間を設定することがありえない、文章の一部のスタイルを変えることがありえない<Label>コンポーネントはそのまま使う。そこにline-heightプロパティは出現し得ず、他のmarginとかbackground-colorとかのプロパティを編集するぶんにはこれで問題ない。NativeScriptには「なるべくフラットにせよ(コンポーネントをネストすればするほど表示が重くなる)」という鉄則があり、無駄に子コンポーネントを持つべきでないため。

<Label>コンポーネントの文章の一部または全体の行間やスタイルを変更したくなったら、<Span>コンポーネントを子コンポーネントとして持つようにする。そしてline-heightは行間ではなく、行も含めた値(font-size: 16ならたとえばline-height: 24だし、font-size: 72ならたとえばline-height: 84とかが実用的な値となる)を設定する。

バグの類だと思うのだが、nativescript-vue@^2.8.3での挙動は以上の通り。南無。