Browse Source

重构首页轮播组件,替换为自定义的carousel实现,删除旧的bootstrap-touch-slider相关文件

懒得勤快 1 month ago
parent
commit
941906b176

+ 80 - 108
src/Masuit.MyBlogs.Core/Views/Home/Index.cshtml

@@ -9,119 +9,91 @@
 @using Microsoft.AspNetCore.Http
 @model Masuit.MyBlogs.Core.Models.ViewModel.HomePageViewModel
 @{
-    ViewBag.Title = "首页";
-    Layout = "~/Views/Shared/_Layout.cshtml";
-    PooledList<FastShare> shares = ViewBag.FastShare;
-    Context.Request.Path = new PathString("/posts");
-    var sliderid = Stopwatch.GetTimestamp().ToBase(62);
+  ViewBag.Title = "首页";
+  Layout = "~/Views/Shared/_Layout.cshtml";
+  PooledList<FastShare> shares = ViewBag.FastShare;
+  Context.Request.Path = new PathString("/posts");
 }
-<link href="~/Assets/banner/bootstrap-touch-slider.css" rel="stylesheet" />
-@if (Model.Banner.Any())
-{
-    <div id="@sliderid" class="carousel bs-slider fade  control-round indicators-line" data-ride="carousel" data-pause="hover" data-interval="5000" id="@Stopwatch.GetTimestamp()">
-        <ol class="carousel-indicators">
-            @for (int i = 0; i < Model.Banner.Count; i++)
-            {
-                if (i == 0)
-                {
-                    <li data-target="#@sliderid" data-slide-to="@i" class="active"></li>
-                }
-                else
-                {
-                    <li data-target="#@sliderid" data-slide-to="@i"></li>
-                }
-            }
-        </ol>
-        <div class="carousel-inner" id="@Stopwatch.GetTimestamp()">
-            @for (int i = 0; i < Model.Banner.Count; i++)
-            {
-                var p = Model.Banner[i];
-                string[] ani = { "zoomInRight", "flipInX", "fadeInLeft", "lightSpeedIn", "fadeInUp", "zoomInLeft", "fadeInRight" };
-                Random r = new Random();
-                <div class="item @(i == 0 ? "active" : "")" class="img-responsive" id="@Stopwatch.GetTimestamp()">
-                    <a asp-controller="Advertisement" asp-action="Redirect" asp-route-id="@p.Id" target="_blank" data-animation="animated @ani[r.Next(ani.Length)]">
-                        <img src="@(p.ImageUrl+"?width=1800&format=webp")" title="@p.Title" alt="@p.Description" decoding="async"/>
-                    </a>
-                </div>
-            }
+<link href="~/Assets/banner/carousel.css" rel="stylesheet"/>
+@if (Model.Banner.Any()) {
+  <div class="carousel" id="@SnowFlake.NewId">
+    @for (int i = 0; i < Model.Banner.Count; i++) {
+      if (i == 0) {
+        <div class="active carousel-slide fade-in">
+          <a href="@Model.Banner[i].Url" target="_blank" title="@Model.Banner[i].Title">
+            <img alt="@(i + 1)" src="@Model.Banner[i].ImageUrl">
+          </a>
         </div>
-        <a class="left carousel-control" href="#@sliderid" role="button" data-slide="prev">
-            <span class="fa fa-angle-left" aria-hidden="true"></span>
-            <span class="sr-only">上一个</span>
-        </a>
-        <a class="right carousel-control" href="#@sliderid" role="button" data-slide="next">
-            <span class="fa fa-angle-right" aria-hidden="true"></span>
-            <span class="sr-only">下一个</span>
-        </a>
-    </div>
+      } else {
+        <div class="carousel-slide">
+          <a href="@Model.Banner[i].Url" target="_blank" title="@Model.Banner[i].Title">
+            <img alt="@(i + 1)" src="@Model.Banner[i].ImageUrl">
+          </a>
+        </div>
+      }
+    }
+    <button class="carousel-btn left">&#9664;</button>
+    <button class="carousel-btn right">&#9654;</button>
+    <div class="carousel-indicator"></div>
+  </div>
 }
-<br />
-<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.touchswipe/1.6.19/jquery.touchSwipe.min.js"></script>
-<script src="~/Assets/banner/bootstrap-touch-slider.min.js"></script>
-@if (Model.Notices.Any())
-{
-    <div class="container notice">
-        <a asp-controller="Notice" asp-action="Details" asp-route-id="@(Model.Notices[0].Id)">
-            <h3 class="size18 text-red text-center">网站最新公告</h3>
-        </a>
-        @Html.Raw(Model.Notices[0].Content.Replace("img src=", $"img class='lazyload' title='{CommonHelper.SystemSettings["Title"]}' alt='{CommonHelper.SystemSettings["Title"]}' decoding='async' loading='lazy' data-src="))
-    </div>
+<br/>
+@if (Model.Notices.Any()) {
+  <div class="container notice">
+    <a asp-action="Details" asp-controller="Notice" asp-route-id="@(Model.Notices[0].Id)">
+      <h3 class="size18 text-center text-red">网站最新公告</h3>
+    </a>
+    @Html.Raw(Model.Notices[0].Content.Replace("img src=", $"img class='lazyload' title='{CommonHelper.SystemSettings["Title"]}' alt='{CommonHelper.SystemSettings["Title"]}' decoding='async' loading='lazy' data-src="))
+  </div>
 }
-@if (shares.Any())
-{
-    <div class="container padding-clear">
-        <div class="panel panel-info">
-            <div class="panel-heading">⚡小撇步Tips⚡</div>
-            <div class="panel-body padding-clear">
-                <table class="table table-bordered margin-clear">
-                    @for (int i = 1; i < shares.Count + 1; i += 2)
-                    {
-                        if (i < shares.Count)
-                        {
-                            <tr>
-                                @if (string.IsNullOrEmpty(shares[i - 1].Link))
-                                {
-                                    <td style="width: 50%">▸ @shares[i - 1].Title</td>
-                                }
-                                else
-                                {
-                                    <td style="width: 50%">▸ <a href="@shares[i - 1].Link" target="_blank">@shares[i - 1].Title</a></td>
-                                }
-                                @if (string.IsNullOrEmpty(shares[i].Link))
-                                {
-                                    <td style="width: 50%">▸ @shares[i].Title</td>
-                                }
-                                else
-                                {
-                                    <td style="width: 50%">▸ <a href="@shares[i].Link" target="_blank">@shares[i].Title</a></td>
-                                }
-                            </tr>
-                        }
-                        else
-                        {
-                            if (string.IsNullOrEmpty(shares[i - 1].Link))
-                            {
-                                <tr>
-                                    <td colspan="2">▸ @shares[i - 1].Title</td>
-                                </tr>
-                            }
-                            else
-                            {
-                                <tr>
-                                    <td colspan="2">▸ <a href="@shares[i - 1].Link" target="_blank">@shares[i - 1].Title</a></td>
-                                </tr>
-                            }
-                        }
-                    }
-                </table>
-            </div>
-        </div>
+@if (shares.Any()) {
+  <div class="container padding-clear">
+    <div class="panel panel-info">
+      <div class="panel-heading">⚡小撇步Tips⚡</div>
+      <div class="padding-clear panel-body">
+        <table class="margin-clear table table-bordered">
+          @for (int i = 1; i < shares.Count + 1; i += 2) {
+            if (i < shares.Count) {
+              <tr>
+                @if (string.IsNullOrEmpty(shares[i - 1].Link)) {
+                  <td style="width: 50%">▸ @shares[i - 1].Title</td>
+                } else {
+                  <td style="width: 50%">
+                    ▸
+                    <a href="@shares[i - 1].Link" target="_blank">@shares[i - 1].Title</a>
+                  </td>
+                }
+                @if (string.IsNullOrEmpty(shares[i].Link)) {
+                  <td style="width: 50%">▸ @shares[i].Title</td>
+                } else {
+                  <td style="width: 50%">
+                    ▸
+                    <a href="@shares[i].Link" target="_blank">@shares[i].Title</a>
+                  </td>
+                }
+              </tr>
+            } else {
+              if (string.IsNullOrEmpty(shares[i - 1].Link)) {
+                <tr>
+                  <td colspan="2">▸ @shares[i - 1].Title</td>
+                </tr>
+              } else {
+                <tr>
+                  <td colspan="2">
+                    ▸
+                    <a href="@shares[i - 1].Link" target="_blank">@shares[i - 1].Title</a>
+                  </td>
+                </tr>
+              }
+            }
+          }
+        </table>
+      </div>
     </div>
+  </div>
 }
 @{
-    var user = Context.Session.Get<UserInfoDto>(SessionKey.UserInfo) ?? new UserInfoDto();
-    await Html.RenderPartialAsync(user.IsAdmin ? "_MainContainer_Admin" : "_MainContainer", Model);
+  var user = Context.Session.Get<UserInfoDto>(SessionKey.UserInfo) ?? new UserInfoDto();
+  await Html.RenderPartialAsync(user.IsAdmin ? "_MainContainer_Admin" : "_MainContainer", Model);
 }
-<script type="text/javascript">
-    $('#@sliderid').bsTouchSlider();
-</script>
+<script src="~/Assets/banner/carousel.js"></script>

+ 0 - 359
src/Masuit.MyBlogs.Core/wwwroot/Assets/banner/bootstrap-touch-slider.css

@@ -1,359 +0,0 @@
-.bs-slider{
-    overflow: hidden;
-    position: relative;
-    min-height: 100px;
-    max-height: 75vh;
-    background: transparent;
-}
-.bs-slider:hover {
-    cursor: -moz-grab;
-    cursor: -webkit-grab;
-}
-.bs-slider:active {
-    cursor: -moz-grabbing;
-    cursor: -webkit-grabbing;
-}
-.bs-slider .bs-slider-overlay {
-    position: absolute;
-    top: 0;
-    left: 0;
-    width: 100%;
-    height: 100%;
-    background-color: rgba(0, 0, 0, 0.40);
-}
-.bs-slider > .carousel-inner > .item > img,
-.bs-slider > .carousel-inner > .item > a > img {
-    margin: auto;
-    width: 100% !important;
-}
-
-/********************
-*****Slide effect
-**********************/
-
-.fade {
-    opacity: 1;
-}
-.fade .item {
-    top: 0;
-    z-index: 1;
-    opacity: 0;
-    width: 100%;
-    position: absolute;
-    left: 0 !important;
-    display: block !important;
-    -webkit-transition: opacity ease-in-out 1s;
-    -moz-transition: opacity ease-in-out 1s;
-    -ms-transition: opacity ease-in-out 1s;
-    -o-transition: opacity ease-in-out 1s;
-    transition: opacity ease-in-out 1s;
-}
-.fade .item:first-child {
-    top: auto;
-    position: relative;
-}
-.fade .item.active {
-    opacity: 1;
-    z-index: 2;
-    -webkit-transition: opacity ease-in-out 1s;
-    -moz-transition: opacity ease-in-out 1s;
-    -ms-transition: opacity ease-in-out 1s;
-    -o-transition: opacity ease-in-out 1s;
-    transition: opacity ease-in-out 1s;
-}
-
-/*---------- LEFT/RIGHT ROUND CONTROL ----------*/
-.control-round .carousel-control {
-    top: 43%;
-    opacity: 0;
-    width: 100px;
-    height: 100px;
-    z-index: 100;
-    color: #ffffff;
-    display: block;
-    font-size: 72px;
-    cursor: pointer;
-    overflow: hidden;
-    line-height: 93px;
-    text-shadow: none;
-    position: absolute;
-    font-weight: normal;
-    background: transparent;
-    -webkit-border-radius: 100px;
-    border-radius: 100px;
-}
-.control-round:hover .carousel-control{
-    opacity: 1;
-}
-.control-round .carousel-control.left {
-    left: 1%;
-}
-.control-round .carousel-control.right {
-    right: 1%;
-}
-.control-round .carousel-control.left:hover,
-.control-round .carousel-control.right:hover{
-    color: #fdfdfd;
-    background: rgba(0, 0, 0, 0.5);
-    border: 0px transparent;
-}
-.control-round .carousel-control.left>span:nth-child(1){
-    left: 45%;
-}
-.control-round .carousel-control.right>span:nth-child(1){
-    right: 45%;
-}
-
-
-
-
-
-/*---------- INDICATORS CONTROL ----------*/
-.indicators-line > .carousel-indicators{
-    right: 45%;
-    bottom: 3%;
-    left: auto;
-    width: 90%;
-    height: 32px;
-    font-size: 0;
-    overflow-x: auto;
-    text-align: right;
-    overflow-y: hidden;
-    padding-left: 10px;
-    padding-right: 10px;
-    padding-top: 1px;
-    white-space: nowrap;
-}
-.indicators-line > .carousel-indicators li{
-    padding: 0;
-    width: 24px;
-    height: 24px;
-    border: 1px solid rgb(158, 158, 158);
-    text-indent: 0;
-    overflow: hidden;
-    text-align: left;
-    position: relative;
-    letter-spacing: 1px;
-    background: rgb(158, 158, 158);
-    -webkit-font-smoothing: antialiased;
-    -webkit-border-radius: 50%;
-    border-radius: 50%;
-    margin-right: 5px;
-    -webkit-transition: all 0.5s cubic-bezier(0.22,0.81,0.01,0.99);
-    transition: all 0.5s cubic-bezier(0.22,0.81,0.01,0.99);
-    z-index: 10;
-    cursor:pointer;
-}
-.indicators-line > .carousel-indicators li:last-child{
-    margin-right: 0;
-}
-.indicators-line > .carousel-indicators .active{
-    margin: 1px 5px 1px 1px;
-    box-shadow: 0 0 0 2px #fff;
-    background-color: transparent;
-    position: relative;
-    -webkit-transition: box-shadow 0.3s ease;
-    -moz-transition: box-shadow 0.3s ease;
-    -o-transition: box-shadow 0.3s ease;
-    transition: box-shadow 0.3s ease;
-    -webkit-transition: background-color 0.3s ease;
-    -moz-transition: background-color 0.3s ease;
-    -o-transition: background-color 0.3s ease;
-    transition: background-color 0.3s ease;
-
-}
-.indicators-line > .carousel-indicators .active:before{
-    transform: scale(0.5);
-    background-color: #fff;
-    content:"";
-    position: absolute;
-    left:-1px;
-    top:-1px;
-    width:24px;
-    height: 24px;
-    border-radius: 50%;
-    -webkit-transition: background-color 0.3s ease;
-    -moz-transition: background-color 0.3s ease;
-    -o-transition: background-color 0.3s ease;
-    transition: background-color 0.3s ease;
-}
-
-
-
-/*---------- SLIDE CAPTION ----------*/
-.slide_style_left {
-    text-align: left !important;
-}
-.slide_style_right {
-    text-align: right !important;
-}
-.slide_style_center {
-    text-align: center !important;
-}
-
-.slide-text {
-    left: 0;
-    top: 25%;
-    right: 0;
-    margin: auto;
-    padding: 10px;
-    position: absolute;
-    text-align: left;
-    padding: 10px 85px;
-}
-
-.slide-text > h1 {
-    
-    padding: 0;
-    color: #ffffff;
-    font-size: 70px;
-    font-style: normal;
-    line-height: 84px;
-    margin-bottom: 30px;
-    letter-spacing: 1px;
-    display: inline-block;
-    -webkit-animation-delay: 0.7s;
-    animation-delay: 0.7s;
-}
-
-.slide-text > h2 {
-    
-    padding: 0;
-    color: #ffffff;
-    font-size: 48px;
-    font-style: normal;
-    line-height: 84px;
-    margin-bottom: 30px;
-    letter-spacing: 1px;
-    display: inline-block;
-    -webkit-animation-delay: 0.7s;
-    animation-delay: 0.7s;
-}
-.slide-text > p {
-    padding: 0;
-    color: #ffffff;
-    font-size: 20px;
-    line-height: 24px;
-    font-weight: 300;
-    margin-bottom: 40px;
-    letter-spacing: 1px;
-    -webkit-animation-delay: 1.1s;
-    animation-delay: 1.1s;
-}
-.slide-text > a.btn-default{
-    color: #000;
-    font-weight: 400;
-    font-size: 13px;
-    line-height: 15px;
-    margin-right: 10px;
-    text-align: center;
-    padding: 17px 30px;
-    white-space: nowrap;
-    letter-spacing: 1px;
-    display: inline-block;
-    border: none;
-    text-transform: uppercase;
-    -webkit-animation-delay: 2s;
-    animation-delay: 2s;
-    -webkit-transition: background 0.3s ease-in-out, color 0.3s ease-in-out;
-    transition: background 0.3s ease-in-out, color 0.3s ease-in-out;
-
-}
-.slide-text > a.btn-primary{
-    color: #ffffff;
-    cursor: pointer;
-    font-weight: 400;
-    font-size: 13px;
-    line-height: 15px;
-    margin-left: 10px;
-    text-align: center;
-    padding: 17px 30px;
-    white-space: nowrap;
-    letter-spacing: 1px;
-    background: #00bfff;
-    display: inline-block;
-    text-decoration: none;
-    text-transform: uppercase;
-    border: none;
-    -webkit-animation-delay: 2s;
-    animation-delay: 2s;
-    -webkit-transition: background 0.3s ease-in-out, color 0.3s ease-in-out;
-    transition: background 0.3s ease-in-out, color 0.3s ease-in-out;
-}
-.slide-text > a:hover,
-.slide-text > a:active {
-    color: #ffffff;
-    background: #222222;
-    -webkit-transition: background 0.5s ease-in-out, color 0.5s ease-in-out;
-    transition: background 0.5s ease-in-out, color 0.5s ease-in-out;
-}
-
-
-
-
-
-
-/*------------------------------------------------------*/
-/* RESPONSIVE
-/*------------------------------------------------------*/
-
-@media (max-width: 991px) {
-    .slide-text h1 {
-        font-size: 40px;
-        line-height: 50px;
-        margin-bottom: 20px;
-    }
-    .slide-text > p {
-
-        font-size: 18px;
-    }
-}
-
-
-/*---------- MEDIA 480px ----------*/
-@media  (max-width: 768px) {
-    .slide-text {
-        padding: 10px 50px;
-    }
-    .slide-text h1 {
-        font-size: 30px;
-        line-height: 40px;
-        margin-bottom: 10px;
-    }
-    .slide-text > p {
-        font-size: 14px;
-        line-height: 20px;
-        margin-bottom: 20px;
-    }
-    .control-round .carousel-control{
-        display: none;
-    }
-
-}
-@media  (max-width: 480px) {
-    .slide-text {
-        padding: 10px 30px;
-    }
-    .slide-text h1 {
-        font-size: 20px;
-        line-height: 25px;
-        margin-bottom: 5px;
-    }
-    .slide-text > p {
-        font-size: 12px;
-        line-height: 18px;
-        margin-bottom: 10px;
-    }
-    .slide-text > a.btn-default, 
-    .slide-text > a.btn-primary {
-        font-size: 10px;
-        line-height: 10px;
-        margin-right: 10px;
-        text-align: center;
-        padding: 10px 15px;
-    }
-    .indicators-line > .carousel-indicators{
-        display: none;
-    }
-
-}

+ 0 - 1
src/Masuit.MyBlogs.Core/wwwroot/Assets/banner/bootstrap-touch-slider.min.js

@@ -1 +0,0 @@
-(function(n){"use strict";n.fn.bsTouchSlider=function(){var t=n(".carousel");return this.each(function(){function i(t){var i="webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend";t.each(function(){var t=n(this),r=t.data("animation");t.addClass(r).one(i,function(){t.removeClass(r)})})}var r=t.find(".item:first").find("[data-animation ^= 'animated']");t.carousel();i(r);t.on("slide.bs.carousel",function(t){var r=n(t.relatedTarget).find("[data-animation ^= 'animated']");i(r)});n(".carousel .carousel-inner").swipe({swipeLeft:function(){this.parent().carousel("next")},swipeRight:function(){this.parent().carousel("prev")},threshold:0})})}})(jQuery);

+ 175 - 0
src/Masuit.MyBlogs.Core/wwwroot/Assets/banner/carousel.css

@@ -0,0 +1,175 @@
+.carousel {
+  width: 100vw;
+  height: 100vh;
+  max-height: 80vh;
+  /* 可自定义最大高度,例如800px,也可以vh、rem等单位 */
+  position: relative;
+  background: #222;
+  overflow: hidden;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.carousel-slide {
+  width: 100vw;
+  height: 100%;
+  position: absolute;
+  top: 0;
+  left: 0;
+  opacity: 0;
+  pointer-events: none;
+  transition: opacity 0.6s;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 1;
+}
+
+.carousel-slide.active {
+  opacity: 1;
+  pointer-events: auto;
+  z-index: 2;
+  position: relative;
+}
+
+.carousel-slide img {
+  width: 100vw;
+  height: 100%;
+  object-fit: cover;
+  box-shadow: 0 4vw 8vw rgba(0, 0, 0, 0.2);
+  display: block;
+}
+
+.carousel-btn {
+  position: absolute;
+  top: 50%;
+  transform: translateY(-50%);
+  width: 4vw;
+  height: 8vw;
+  background: rgba(0, 0, 0, 0.3);
+  color: #fff;
+  font-size: 3vw;
+  border: none;
+  cursor: pointer;
+  z-index: 10;
+  border-radius: 1vw;
+  transition: background 0.2s;
+  user-select: none;
+}
+
+.carousel-btn:hover {
+  background: rgba(0, 0, 0, 0.6);
+}
+
+.carousel-btn.left {
+  left: 2vw;
+}
+
+.carousel-btn.right {
+  right: 2vw;
+}
+
+.fade-in {
+  animation: fadeIn 1s;
+}
+
+.slide-in-left {
+  animation: slideInLeft 1s;
+}
+
+.slide-in-right {
+  animation: slideInRight 1s;
+}
+
+.zoom-in {
+  animation: zoomIn 1s;
+}
+
+.rotate-in {
+  animation: rotateIn 1s;
+}
+
+@keyframes fadeIn {
+  from {
+    opacity: 0;
+  }
+
+  to {
+    opacity: 1;
+  }
+}
+
+@keyframes slideInLeft {
+  from {
+    transform: translateX(-100vw);
+    opacity: 0;
+  }
+
+  to {
+    transform: translateX(0);
+    opacity: 1;
+  }
+}
+
+@keyframes slideInRight {
+  from {
+    transform: translateX(100vw);
+    opacity: 0;
+  }
+
+  to {
+    transform: translateX(0);
+    opacity: 1;
+  }
+}
+
+@keyframes zoomIn {
+  from {
+    transform: scale(0.2);
+    opacity: 0;
+  }
+
+  to {
+    transform: scale(1);
+    opacity: 1;
+  }
+}
+
+@keyframes rotateIn {
+  from {
+    transform: rotate(-180deg) scale(0.5);
+    opacity: 0;
+  }
+
+  to {
+    transform: rotate(0deg) scale(1);
+    opacity: 1;
+  }
+}
+
+.carousel-indicator {
+  position: absolute;
+  bottom: 4vw;
+  left: 50%;
+  transform: translateX(-50%);
+  display: flex;
+  gap: 1vw;
+  z-index: 10;
+  justify-content: center;
+  align-items: center;
+}
+
+.carousel-indicator span {
+  display: block;
+  width: 1vw;
+  height: 1vw;
+  background: #aaa;
+  border-radius: 50%;
+  transition: background 0.3s;
+  cursor: pointer;
+}
+
+.carousel-indicator .active {
+  background: #fff;
+}

+ 79 - 0
src/Masuit.MyBlogs.Core/wwwroot/Assets/banner/carousel.js

@@ -0,0 +1,79 @@
+const slides = document.querySelectorAll('.carousel-slide');
+const leftBtn = document.querySelector('.carousel-btn.left');
+const rightBtn = document.querySelector('.carousel-btn.right');
+const indicator = document.querySelector('.carousel-indicator');
+const animationClasses = ['fade-in', 'slide-in-left', 'slide-in-right', 'zoom-in', 'rotate-in'];
+let current = 0, timer;
+
+function setIndicator() {
+  indicator.innerHTML = '';
+  for (let i = 0; i < slides.length; i++) {
+    const dot = document.createElement('span');
+    dot.className = i === current ? 'active' : '';
+    dot.onclick = () => showSlide(i, true);
+    indicator.appendChild(dot);
+  }
+}
+
+function randomAnimation(index, direction = null) {
+  let classes = animationClasses.slice();
+  if (direction === 'left') {
+    classes = ['slide-in-left', 'fade-in', 'zoom-in', 'rotate-in'];
+  } else if (direction === 'right') {
+    classes = ['slide-in-right', 'fade-in', 'zoom-in', 'rotate-in'];
+  }
+  return classes[Math.floor(Math.random() * classes.length)];
+}
+
+function clearAnimation(i) {
+  animationClasses.forEach(cls => slides[i].classList.remove(cls));
+}
+
+function showSlide(idx, manual = false, direction = null) {
+  if (idx === current) return;
+  // 自动方向判断(点击指示器时更优体验)
+  if (manual && direction == null) {
+    direction = idx > current ? 'right' : 'left';
+  }
+  clearAnimation(current);
+  slides[current].classList.remove('active');
+  clearAnimation(idx);
+  slides[idx].classList.add('active');
+  slides[idx].classList.add(randomAnimation(idx, direction));
+  setIndicator();
+  current = idx;
+  if (timer) clearInterval(timer);
+  timer = setInterval(() => nextSlide(), 5000);
+}
+
+function nextSlide() {
+  let nextIdx = (current + 1) % slides.length;
+  showSlide(nextIdx, false, 'right');
+}
+
+function prevSlide() {
+  let prevIdx = (current - 1 + slides.length) % slides.length;
+  showSlide(prevIdx, false, 'left');
+}
+
+leftBtn.onclick = () => prevSlide();
+rightBtn.onclick = () => nextSlide();
+
+setIndicator();
+timer = setInterval(() => nextSlide(), 5000);
+
+// 支持触屏左右滑动
+let startX = 0;
+document.querySelector('.carousel').addEventListener('touchstart', e => {
+  startX = e.touches[0].clientX;
+});
+document.querySelector('.carousel').addEventListener('touchend', e => {
+  let endX = e.changedTouches[0].clientX;
+  if (endX - startX > 50) prevSlide();
+  else if (endX - startX < -50) nextSlide();
+});
+
+// 动画结束后清理动画class
+slides.forEach((slide, idx) => {
+  slide.addEventListener('animationend', () => clearAnimation(idx));
+});