需求描述

image.png
如图, 有4个区域需要注意

  1. 影片选择区, 要能获取到选择的影片对应的价格.
  2. 标记示例区, 与下方的图标颜色形状大小一致, 但只是示例, 不参与用户交互, 不与选座数据混淆.
  3. 荧幕区, 用css模拟出第一视角的观看体验.
  4. 座位区, 白色为不可选, 灰色为可选, 选中后变为蓝色, 再次点击变为灰色.
  5. 数据统计区, 能统计已选座位数, 并根据单价计算总费用.

此外, 选中的影片, 座位序列和总票价需要存入本地存储.

实现

HTML部分

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <link rel="stylesheet" href="style.css">
  6. <title>电影座位预订</title>
  7. </head>
  8. <body>
  9. <h1>欢迎来到米修影院</h1>
  10. <div class="movie-container">
  11. <label for="movie">选择影片</label>
  12. <select id="movie">
  13. <option value="32">寄生虫 (票价: 32元)</option>
  14. <option value="35">小丑 (票价: 35元)</option>
  15. <option value="38">好莱坞往事 (票价: 38元)</option>
  16. <option value="30">玩具总动员 (票价: 30元)</option>
  17. </select>
  18. </div>
  19. <ul class="showcase">
  20. <li>
  21. <div class="seat"></div>
  22. <small>可选</small></li>
  23. <li>
  24. <div class="seat selected"></div>
  25. <small>已选</small></li>
  26. <li>
  27. <div class="seat occupied"></div>
  28. <small>不可选</small></li>
  29. </ul>
  30. <div class="container">
  31. <div class="screen"></div>
  32. <div class="row">
  33. <div class="seat"></div>
  34. <div class="seat"></div>
  35. <div class="seat"></div>
  36. <div class="seat"></div>
  37. <div class="seat"></div>
  38. <div class="seat"></div>
  39. <div class="seat"></div>
  40. <div class="seat"></div>
  41. </div>
  42. <div class="row">
  43. <div class="seat"></div>
  44. <div class="seat"></div>
  45. <div class="seat"></div>
  46. <div class="seat occupied"></div>
  47. <div class="seat occupied"></div>
  48. <div class="seat"></div>
  49. <div class="seat"></div>
  50. <div class="seat"></div>
  51. </div>
  52. <div class="row">
  53. <div class="seat"></div>
  54. <div class="seat"></div>
  55. <div class="seat"></div>
  56. <div class="seat"></div>
  57. <div class="seat"></div>
  58. <div class="seat"></div>
  59. <div class="seat occupied"></div>
  60. <div class="seat occupied"></div>
  61. </div>
  62. <div class="row">
  63. <div class="seat"></div>
  64. <div class="seat"></div>
  65. <div class="seat"></div>
  66. <div class="seat"></div>
  67. <div class="seat"></div>
  68. <div class="seat"></div>
  69. <div class="seat"></div>
  70. <div class="seat"></div>
  71. </div>
  72. <div class="row">
  73. <div class="seat"></div>
  74. <div class="seat"></div>
  75. <div class="seat"></div>
  76. <div class="seat occupied"></div>
  77. <div class="seat occupied"></div>
  78. <div class="seat"></div>
  79. <div class="seat"></div>
  80. <div class="seat"></div>
  81. </div>
  82. <div class="row">
  83. <div class="seat"></div>
  84. <div class="seat"></div>
  85. <div class="seat"></div>
  86. <div class="seat"></div>
  87. <div class="seat occupied"></div>
  88. <div class="seat occupied"></div>
  89. <div class="seat occupied"></div>
  90. <div class="seat"></div>
  91. </div>
  92. </div>
  93. <p class="text">
  94. 您已经选择了 <span id="count">0</span> 个座位, 总计票价为 <span id="total">0</span>
  95. </p>
  96. <script src="script.js"></script>
  97. </body>
  98. </html>

这个HTML感觉写得有点蠢. 先实现功能吧, 后面有时间再来优化.

CSS部分

  1. * {
  2. margin: 0;
  3. padding: 0;
  4. box-sizing: border-box;
  5. }
  6. body {
  7. background: #242333;
  8. display: flex;
  9. flex-direction: column;
  10. align-items: center;
  11. justify-content: center;
  12. min-height: 100vh;
  13. font-family: "Helvetica", "Arial", sans-serif;
  14. }
  15. .movie-container {
  16. margin: 20px 0;
  17. }
  18. .movie-container select {
  19. background-color: #fff;
  20. border: 0;
  21. border-radius: 5px;
  22. font-size: 14px;
  23. margin-left: 10px;
  24. padding: 5px 15px;
  25. /*以下是对于苹果系统的设置, 不设置的话padding会没有效果*/
  26. -moz-appearance: none;
  27. -webkit-appearance: none;
  28. appearance: none;
  29. }
  30. .seat {
  31. height: 12px;
  32. width: 15px;
  33. background-color: #444451;
  34. margin: 3px;
  35. border-top-right-radius: 10px;
  36. border-top-left-radius: 10px;
  37. }
  38. .seat.selected {
  39. background-color: #6feaf6;
  40. }
  41. .seat.occupied {
  42. background-color: #fff;
  43. }
  44. /*选中从前往后第2个*/
  45. .seat:nth-of-type(2) {
  46. margin-right: 18px;
  47. }
  48. /*选中从后往前第二个*/
  49. .seat:nth-last-of-type(2) {
  50. margin-left: 18px;
  51. }
  52. /*可选和已选都有鼠标移入动画, 这里用:not()把 .occupied去除掉*/
  53. .container .seat:not(.occupied):hover {
  54. cursor: pointer;
  55. transform: scale(1.2);
  56. }
  57. /*.seat:not(.occupied) {*/
  58. /* transition: all 0.2s ease;*/
  59. /*}*/
  60. .showcase {
  61. background-color: rgba(0, 0, 0, 0.1);
  62. padding: 5px 10px;
  63. border-radius: 5px;
  64. color: #777;
  65. list-style-type: none;
  66. display: flex;
  67. justify-content: space-between;
  68. margin-bottom: 10px;
  69. }
  70. .showcase li {
  71. display: flex;
  72. align-items: center;
  73. justify-content: center;
  74. margin: 0 10px;
  75. }
  76. .showcase li small {
  77. margin-left: 2px;
  78. }
  79. .container {
  80. /*添加视距*/
  81. perspective: 1000px;
  82. margin-bottom: 10px;
  83. }
  84. .screen {
  85. background-color: #fff;
  86. height: 70px;
  87. width: 100%;
  88. margin: 15px 0;
  89. transform: rotateX(-45deg); /*在父元素中添加视距才能看到3D效果*/
  90. box-shadow: 0 3px 10px rgba(255, 255, 255, 0.7);
  91. }
  92. .row {
  93. display: flex;
  94. }
  95. .text {
  96. margin: 5px 0;
  97. }
  98. .text span {
  99. color: #6feaf6;
  100. }

这里的重点: 对苹果系统的兼容( apperance 属性), 伪类选择器 :nth-of-type(n):not() 的应用, transform-totateperspective的结合应用.

JavaScript部分

  1. const container = document.querySelector('.container')
  2. const seats = document.querySelectorAll('.row .seat:not(.occupied)')
  3. const count = document.getElementById('count')
  4. const total = document.getElementById('total')
  5. const movieSelect = document.getElementById('movie')
  6. //value是字符串, 用+号饮食转换成Number
  7. let ticketPrice = +movieSelect.value
  8. //初始化渲染座位和影片
  9. populateUi()
  10. //更新座位数及总票价
  11. function updateSelectedCount(){
  12. //获取已选座位的数组
  13. const selectedSeats = document.querySelectorAll('.row .seat.selected')
  14. //map()方法会像forEach()那样遍历一个数组, 然后对每项处理后返回一个新的数组
  15. const seatsIndex = [...selectedSeats].map(seat => [...seats].indexOf(seat))
  16. localStorage.setItem('selectedSeats', JSON.stringify(seatsIndex))
  17. const selectedSeatsCount = selectedSeats.length
  18. count.innerText = selectedSeatsCount.toString()
  19. total.innerText = (selectedSeatsCount * ticketPrice).toString()
  20. }
  21. //保存电影索引值和票价
  22. function setMovieData(movieIndex, moviePrice){
  23. localStorage.setItem('selectedMovieIndex', movieIndex)
  24. localStorage.setItem('selectedMoviePrice', moviePrice)
  25. }
  26. //封装函数获取本地数据并渲染样式
  27. function populateUi(){
  28. //获取座位信息
  29. const selectedSeats = JSON.parse(localStorage.getItem('selectedSeats'))
  30. if (selectedSeats !== null && selectedSeats.length > 0){
  31. seats.forEach((seat, index) => {
  32. if (selectedSeats.indexOf(index) > -1){
  33. seat.classList.add('selected')
  34. }
  35. })
  36. }
  37. //获取影片信息
  38. const selectedMovieIndex = localStorage.getItem('selectedMovieIndex')
  39. console.log(selectedMovieIndex)
  40. if (selectedMovieIndex !== null){
  41. movieSelect.selectedIndex = selectedMovieIndex
  42. }
  43. }
  44. //监听电影下拉框
  45. movieSelect. addEventListener('change', e => {
  46. ticketPrice = +e.target.value
  47. setMovieData(e.target.selectedIndex, e.target.value)
  48. updateSelectedCount()
  49. })
  50. //绑定座位点击事件, 时间冒泡
  51. container.addEventListener('click', e => {
  52. // console.log(e.target)
  53. //使用classList数组的contains方法, 检测数组中是否包含某一项
  54. if (e.target.classList.contains('seat') &&
  55. !e.target.classList.contains('occupied')){
  56. //使用classList 的toggle方法, 有则删除, 无则加入
  57. e.target.classList.toggle('selected')
  58. updateSelectedCount()
  59. }
  60. })
  61. //设置初始座位和总票价
  62. updateSelectedCount()

这里有一个重要bug, 页面加载完数据后, 再切换影片, 再刷新页面, 显示的还是之前的票价, 待修复.