Day 29:「欸!你填一下这张表!」- 弹跳视窗、Modal

Day29-Banner

「注意,您的表单尚未填写完成」

好,我知道了。 (按下确定)

欸? 为什麽关不掉!?
不是按下确定就要关掉了吗?

这是谁做的烂东西啊,看我怎麽整死你!


 

(开发者人员工具打开後,兔兔惊呆。)

哦 ... 什麽嘛~原来是我之前的未完成品啊~ 其实也不是做得很烂啦! 只是那时候比较赶时间所以 ...

「兔兔,我们都听到罗~」

齁,不是这样的,就,呃 ... 好啦!

(咳咳!)
那看来今天的任务就是把它做完整,来雪耻了!!!

carrotPoint 建立元件

首先,在专案里的 ./src/components 资料夹中新增一个 Modal.vue 的元件:

然後:

<template>
  
</template>

<script>
export default {
  name: "Modal",
}
</script>

为了成品的美观,修改一下 App.vue 内的样式,接着把元件新增到画面中:

<template>
  <div :class="[
    'w-screen h-screen',
    'p-5'
  ]">
    <Modal />
  </div>
</template>

<script>
import Modal from './components/Modal.vue'

export default {
  data() {
    return {

    }
  },
  components: {
    Modal,
  }
}
</script>

OK,下一步!
 

carrotPoint 互动视窗 (Modal)

今天要参考的范本是 ...

有没有觉得很面熟啦~

没错,就是我们的 Creator
今天要做出来的成品和 Creator 的 Modal 本身没有什麽差异哦~

首先,来分析一下要素:

主要类别就可以分成後面的覆盖层,和前面的视窗两类。 但视窗其实也还能再简易的切一下:

这样推理线索都具备後,就可以开始名侦探兔兔的神还原了!

 

carrotPoint 还原现场

依据各个线索来推断,可以得到下面的结论:

  • Modal 覆盖层: 压在後面,看起来是用了半透明满版
  • Modal 视窗: 从外观看起来是完全置中於画面,压在覆盖层之上,然後 ... 那完美弧度,应该是传说中的小米定律导致的
    • 视窗标题列: 里面的左、右各有一个元素,看来是和手风琴选单相似的犯罪手法
    • 视窗内容区: 通常 Modal 都会一直变换其中的内容 ... 可能是因为用了 slot 的原因

「我应该没有漏说的吧?因为 ... 真相永远只有一个!」

当然 ... 因为前一次造下这犯行的人就是我自己啊 QQ

为了雪耻当然得更认真点!

来吧! 第一个先完成覆盖层和视窗的基底:

<div class="fixed top-0 left-0 w-screen h-screen p-5 flex justify-center items-center">
  <!-- Modal-Overlay -->
  <div class="absolute top-0 left-0 w-full h-full bg-black/50" />

  <!-- Modal-Window -->
  <div class="w-full max-w-sm bg-white rounded-md overflow-hidden z-10">
    这是 Modal 视窗
  </div>
</div>

对了,有注意到上面覆盖层的 bg-black/50 吗? 这就是之前在 Day 16 说过的便捷语法,快速就可以上完颜色和不透明度了。

还有,因为覆盖层的位置是 absolute,所以会盖住视窗,记得加上 z-10 来调整 z 轴位置,把视窗的显示层级往上拉。

那上面都完成了,就可以准备来刻视窗内部了。

其实视窗的结构和 Day 27 的手风琴选单极为类似,大部分东西都是一样的,而视窗内容区块的设计也与手风琴选单内容区的设计相同,移植过来再修改一下,就会像这样子:

<!-- Modal-Window -->
<div class="w-full max-w-sm bg-white rounded-md overflow-hidden z-10">
  <div class="border-b-2 p-3 flex justify-between items-center">
    <div class="font-bold text-gray-700">
      注意
    </div>
    <div class="h-7 w-7 p-1 hover:bg-gray-200 active:scale-90 rounded-md cursor-pointer transition-all">
      <svg xmlns="http://www.w3.org/2000/svg" class="h-full w-full" fill="none" viewBox="0 0 24 24" stroke="currentColor">
        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
      </svg>
    </div>
  </div>

  <div>
    <slot name="itemContent">
      <div class="p-3">
        <slot name="itemText">
          这是 Modal 视窗
        </slot>
      </div>
    </slot>
  </div>
</div>

顺便把视窗标题的文字变成 Props,这样们就可以从外部修改了:

<!-- 应用时 -->
<Modal title="嗨">
  <template v-slot:itemText>
    你好
  </template>
</Modal>

完美至极!

接下来就要来完成我们最需要雪耻的 Modal 视窗开关部份了!

 

carrotPoint 一雪前耻?

为了还要可以再度开启视窗,我们会用上 Day 26 做过的按钮哦~

我们就,开始吧!

首先,先来完成关闭的功能。 能够用来点击後关闭视窗的不外乎就是覆盖层和叉叉两处,但为了储存状态,我们在 data 中要增加一个变数:

export default {
  name: "Modal",
  props: {
    title: {
      default: "注意"
    }
  },
  data() {
    return {
      showed: true,
    }
  },
}

先设为 true 哦!
不然等等一开始 Modal 就消失了 XD

然後写一个 methods 的函数 closing() 来处理关闭事件:

export default {
  name: "Modal",
  props: {
    title: {
      default: "注意"
    }
  },
  data() {
    return {
      showed: true,
    }
  },
+ methods: {
+   closing() {
+     this.showed = false
+   }
+ }
}

然後把我们所写的函数应用上 template 中的覆盖层和叉叉,做为它们 onclick 事件所触发的动作:

<div class="fixed top-0 left-0 w-screen h-screen p-5 flex justify-center items-center">
  <!-- Modal-Overlay -->
  <div
    class="absolute top-0 left-0 w-full h-full bg-black/50"
    @click="closing()"
  />

  <!-- Modal-Window -->
  <div class="w-full max-w-sm bg-white rounded-md overflow-hidden z-10">
    <div class="border-b-2 p-3 flex justify-between items-center">
      <div class="font-bold text-gray-700">
        {{ title }}
      </div>
      <div
        class="h-7 w-7 p-1 hover:bg-gray-200 active:scale-90 rounded-md cursor-pointer transition-all"
        @click="closing()"
      >
        <svg xmlns="http://www.w3.org/2000/svg" class="h-full w-full" fill="none" viewBox="0 0 24 24" stroke="currentColor">
          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
        </svg>
      </div>
    </div>

    <div>
      <slot name="itemContent">
        <div class="p-3">
          <slot name="itemText">
            这是 Modal 视窗
          </slot>
        </div>
      </slot>
    </div>
  </div>
</div>

但现在按了仍然还不会有效果,因为我们还没有让 Modal 有相对应的样式变化。

不过因为兔兔的做法和外面一般的实现方法有些许不同,所以在这里先介绍一个会用到的特殊 CSS 属性: pointer-events

pointer-events

在 CSS 中,若想要让点击事件穿透到元素背後,使这个元素不会触发点击事件的话,pointer-events 就是你要找的功能。

在 Tailwind 中也有相对应的功能性 class,若要让点击穿透,就在该元素中使用 pointer-events-none,若要恢复,则是 pointer-events-auto

所以在这里,兔兔决定若是不显示 Modal 时,Modal 整体会变成完全透明且可点击穿透显示 Modal 时则完全不透明且不可点击穿透。我们用三元来实现:

<div
  :class="[
    'fixed top-0 left-0',
    'w-screen h-screen',
    'p-5',
    'flex justify-center items-center',
    'transition-all duration-300',
    showed? 'opacity-100': 'opacity-0 pointer-events-none'
  ]"
>
    <!-- Modal-Overlay -->
    <div
      class="absolute top-0 left-0 w-full h-full bg-black/50"
      @click="closing()"
    />
    
    <!-- Modal-Window -->
  ...

做到这里,去点击 Modal 的叉叉或覆盖层应该马上就有效果了,Modal 会淡出。

不过这样还不够华丽,我们顺便追加一下 Modal 视窗的动画。

Modal 视窗的部份我们让它在**不显示 Modal **时,会缩放到 0;相反的,显示 Modal 时,缩放程度会回到 100,也就是不缩放。这个我们也用三元运算子来做:

<!-- Modal-Window -->
<div
  :class="[
    'w-full max-w-sm bg-white rounded-md overflow-hidden z-10',
    showed? 'scale-100': 'scale-0',
    'transition-all duration-300'
  ]"
>
  <div class="border-b-2 p-3 flex justify-between items-center">
    <div class="font-bold text-gray-700">
      {{ title }}
    </div>
    <div
      class="h-7 w-7 p-1 hover:bg-gray-200 active:scale-90 rounded-md cursor-pointer transition-all"
      @click="closing()"
    >
      <svg xmlns="http://www.w3.org/2000/svg" class="h-full w-full" fill="none" viewBox="0 0 24 24" stroke="currentColor">
        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
      </svg>
    </div>
  </div>
  ...

哈! 这样是不是很好看呢?

现在可以关起来了,可是却打不开 ...

不过也不用担心啦,把我们之前做的按钮拿出来用,让我们按下按钮来打开 Modal 吧!

但是要可以从外部开启,我们还必须在内部动一点手脚。

要可以从外部收取是否展开 Modal 的讯号,我们就要加上一个 Props 参数:

export default {
  name: "Modal",
  props: {
    showModal: {},
    title: {
      default: "注意"
    }
  },
  ...
}

加好之後,别忘了做进度条时的事情,要监控外部传来的变数内容来更新内部的变数,还有在元件挂载时也要做一次:

export default {
  name: "Modal",
  props: {
    showModal: {},
    title: {
      default: "注意"
    }
  },
  data() {
    return {
      showed: true,
    }
  },
  mounted() {
    this.showed = this.showModal
  },
  watch: {
    showModal(newVal, oldVal) {
      this.showed = newVal
    }
  },
  methods: {
    closing() {
      this.showed = false
    }
  }
}

虽然这样看似完整了,不过还有一个小 bug。 我们在关闭事件 closing() 触发时一直都是改变内部的变数,但外部的状态并不会被我们同步到,所以我们必须用 emit 传递出去给父层:

methods: {
  closing() {
    this.showed = false
    this.$emit("closing")
  }
}

OK,这样 Modal 元件应该是完成罗!

接着来测试吧~
 

carrotPoint 测试时间

我们用之前做过的按钮快速来建立个测试环境。

我们必须现在 App.vue 中定义一个储存 Modal 开启状态的变数,按钮按下时会把变数变为 true,接收到来自 Modal 关闭事件触发时,会把变数设为 false。

那实作起来大概就是这样:

<template>
  <div :class="[
    'w-screen h-screen',
    'p-5'
  ]">
    <RabbitButton
      class="w-40"
      text="开启 Modal"
      color="yellow"
      @click="showed=true"
    />

    <Modal
      title="嗨"
      :showModal="showed"
      @closing="showed=false"
    >
      <template v-slot:itemText>
        兔兔你好
      </template>
    </Modal>
  </div>
</template>

<script>
import RabbitButton from './components/RabbitButton.vue'
import Modal from './components/Modal.vue'

export default {
  data() {
    return {
      showed: true,
    }
  },
  components: {
    RabbitButton,
    Modal,
  }
}
</script>

OK~ 这样算是雪耻成功了吧?
 

还行吗? 会不会太难呢?

其实你应该会发现写久了,复杂度好像就那样而已,更多的是样式上的变化,还有怎麽做才会让自己未来可以更加的方便,其实人家说好的程序,是要能一直往上扩充,而不是去反覆修改原有的内容。

希望大家都能做顺利的自己造轮子嘿!
(如果开轮胎厂了,记得要让兔兔当股东。)

carrotPoint 给你们的回家作业:


关於兔兔们:


 


( # 兔兔小声说 )

明天就是最後一天了呢~
说实在一路走来心里是很挣扎的,
因为根本就没想过可以坚持到这里,
而且,我这才发现自己在文章内容上的坑越挖越大了 XDDD

加油!


<<:  Day15:SwiftUI—TabView

>>:  【後转前要多久】# Day15 CSS -难东西 (伪元素)

项目清单-30天学会HTML+CSS,制作精美网站

项目清单分为条列式清(ol)单及编号清单(ul),两者的差别在於是否有自动排序项目的功能,<u...

我们的基因体时代-AI, Data和生物资讯 Day08-合成生物学与机器学习

上一篇我们的基因体时代-AI, Data和生物资讯 Day07- 蛋白质结构和机器学习02:Alph...

[区块链&DAPP介绍 Day4] 第一个智能合约

今天我们来实作第一个智能合约看看 首先,要发布智能合约一定就要就要有区块链,我们也不可能直接就真金白...

Day28 - Linux 编译 POC/exploit

复习:渗透测试的目的 在合法委托下,确认目标网站或系统有可利用的漏洞,若确认有目标在取得授权下,提升...

Android 手机 行动电话 小人图示 talkback 无障碍按钮 导览列 捷径 协助工具按钮开关 设定 隐藏 开启

Android 手机 行动电话 小人图示 talkback 无障碍按钮 导览列 捷径 协助工具按钮开...