Giter Site home page Giter Site logo

dev-zuo / nice-func Goto Github PK

View Code? Open in Web Editor NEW
129.0 1.0 36.0 135.74 MB

尝试实现一些让人耳目一新、感觉很 nice 的网页内容、动效 http://nice.zuo11.com

Home Page: http://nice.zuo11.com

License: MIT License

JavaScript 93.30% HTML 5.30% CSS 0.09% Less 1.31%
animation gsap

nice-func's Introduction

nice-func

尝试实现一些让人耳目一新、感觉很 nice 的网页内容、动效

5. 苹果官网 AirPods 充电盒动效实现,根据滚动位置打开、收起、旋转等

核心:Play video on scroll + 根据 video 播放位置,设置不同文案显示

来源:AirPods Pro (第二代) - Apple (**大陆)

5-1-AirPods.gif

最开始是在研究 ipad mini 根据页面滚动旋转操作入坑。了解到是一个视频。根据滚动播放视频。

5-2-ipad-mini.gif

后面发现 AirPods Pro,之前老款的 Mac Pro 垃圾桶版也有类似的动画,先解决第一个问题,根据滚动播放视频

  • 1、使用下载下来的 ipad mini webm 视频,不流畅、laggy(迟缓)

  • 2、使用 FFmepg 设置关键帧间隔为 1,三种方式,都可以保持流畅。但 webm 转 mp4 透明背景会被转换为黑色无法去除

    // if somebody needs a command to convert I frame only mp4(video with keyframe distance), please refer to the command below.
    
    ffmpeg -i original.mp4 -c:v libx264 -x264-params keyint=1 original_I.mp4
    
    // keyint是两个keyframe之间的最大距离,minkeyint是两个 keyframe之间的最小距离。
    
    // 什么是视频关键帧距? // https://www.jianshu.com/p/1a6b2bff5876
    // 关键帧距离值会告诉编码器有关重新评估视频图像,以及将完整帧或关键帧录制到文件中的频率。
    // 如果画面包含大量场景变换或迅速移动的动作或动画,那么减少关键帧距离将会提高图像的整体品质。
    // 一个较小的关键帧距离对应于一个较大的输出文件。
    • 参考:Why it's laggy when is scroll up dkaoster/scrolly-video#36
    • webm 支持透明背景,mp4 不支持透明背景,因此转换后透明背景被转黑色
  • 3、使用 Honeycam 将黑色背景替换为白色背景,有黑色噪点,非透明

  • 4、使用 Captura 录制白色背景视频,滚动切换帧流畅,但比官网模糊、非透明(设置 #f5f5f7 背景视频大部分还是白色)

  • 5、回到 1,把 ipad mini 官网 webm 视频拿出来,看怎么将 webm 设置关键帧距离为 1,且保持 webm 透明背景?

    • 暂时无解
// 视频播放时长、视频当前播放位置
// select video element
var vid = document.getElementById('vid');
window.onscroll = function(){
    console.log(vid.duration ); //获取视频时长
    console.log(vid.currentTime); //获取视频当前播放时间
};

经过测试,发现 AirPods 下面的视频比较流程,于是选中了 AirPods 充电盒动效作为主题

上面有三种方式实现在滚动时切换视频,由于我们需要在滚动的时候,根据视频播放位置显示不同的文案,这里监听视频播放位置,再做处理

vid.addEventListener("timeupdate", (e) => {
  // 0 - 2s 充电盒关闭,隐藏底部文案
  // 2 - 3s 充电盒旋转到侧面,显示充电盒侧面文案
  // 3 - 4s 充电盒旋转到底部,隐藏侧面文案
  // 4 - 4.5s 显示底部文案
  // 4.5. - 5s 充电盒位置回正,显示底部文案
  console.log(e.target.currentTime);
  let t = e.target.currentTime;

  // step-1
  if (t < 0.2) {
    document.querySelector(".step-1").classList.add("active");
  } else {
    document.querySelector(".step-1").classList.remove("active");
  }

  // step-2
  if (t > 2 && t < 3.2) {
    document.querySelector(".step-2").classList.add("active");
    // 精确控制宽度显示
    // 1s 间隔, 宽度 0 => 429;1s = 429、(t - 2)s = (t - 2) * 429
    //          top  50 => 0; 1s = 0、 (t - 2)s =
    // document.querySelector(".step-2 .line").style.clipPath = `inset(0 0 0 ${(3 - t) * 100}%)`
  } else {
    document.querySelector(".step-2").classList.remove("active");
  }
})

参考:

4. iPhone14 新配色官网首屏动效实现

核心 transform: matrix /ˈmeɪtrɪks/

来源:iPhone 14 和 iPhone 14 Plus - Apple (**大陆)

4-1-iphone14-yellow.gif

// 图片间距,靠 scale: 56% 这种缩放做出来的
// 图片白色边框去除,使用 mask-image

// focus 黄色 iphone 时,父元素样式
transform: matrix(1.04327, -0.464493, 0.464493, 1.04327, -768.586, 130.872);
// transform matrix重点解析 https://zhuanlan.zhihu.com/p/74279787
// rotate、scale、translate 3 个矩阵相乘,得到上面的 matrix 的 6 个参数
transform: rotate(-24deg) scale(1.142) translate(-661px, -170px);
// 由于 matrix 结果很难逆向,使用手动方式,测试出对应的角度,偏移具体值。

transform: matrix(0.86, 0, 0, 0.86, 0, 0);
// 对应矩阵如下,等价于 scale(0.86, 0.86):
0.86 0 0
0 0.86 0
0 0 1

transform: matrix(1, 0, 0, 1, 0, -2480.12);
// translateY(Y):
1 0 0
0 1 -2480.12
0 0 1
// 紫色 matrix 逆向
transform: matrix(1.3532, -0.192071, 0.192071, 1.3532, -649.839, 140.245);
transform: scale(1.38) translate(-462px, 95px) rotate(-9deg); // 手工测试
// 1、应用该样式,记录元素宽高,再通过设置 scale 调整达到对应宽高,得到 scale(x) 值
// 2、观察 iphone 角度,得到 rotate  
// 3、根据位置得到 x, y 偏移

// 根据 mdn 文档来看 https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/matrix
transform: matrix(scaleX(), skewY(), skewX(), scaleY(), translateX(), translateY())
// 尝试转换 purple 位置,发现有偏差,还是按照之前的手动还原来还原值
transform: matrix(1.3532, -0.192071, 0.192071, 1.3532, -649.839, 140.245);
transform: scale(1.3532) skewY(-0.192071deg) skewX(0.192071deg) translate(-649.839px, 140.245px);

// 蓝色
transform: matrix(1.60574, 0.269279, -0.269279, 1.60574, -510.662, 196.845);
transform: scale(1.619) rotate(10deg) translate(-302px, 173px);
// 红色
transform: matrix(1.64561, 0.755092, -0.755092, 1.64561, -364.436, 307.051);
transform: scale(1.811) rotate(24deg) translate(-120px, 241px);

进入页面后动画

const colorsMap = {
  midnight: "午夜色",
  starlight: "星光色",
  red: "红色",
  blue: "蓝色",
  purple: "紫色",
  yellow:  "黄色"
};

window.addEventListener('load', async () => {
  // 页面加载完成后,逐一显示 iphone 图片
  for (let color in colorsMap) {
    console.log('start')
    // await new Promise((resolve, reject) => {
    //   setTimeout(() => {
    //     resolve()
    //   }, 50)
    // })
    await new Promise((r) => setTimeout(r, 50))
    console.log(color)
    document.querySelector(`.iphone-images .${color}`).style.opacity = '1';
  }
  await new Promise((r) => setTimeout(r, 500)) // 停留 0.5s
  document.querySelector('#yellow').click() // focus 到黄色
  document.querySelector('.new-style').style.display = 'block'; // 显示新款文案
})

切换颜色

// 当 colors 点击,如果是 span 元素
// 现将所有的 label 上的 active 移除。在将其 parent 节点加上 active
document.querySelector('.colors').addEventListener('click', (e) => {
  if (e.target.nodeName === 'SPAN') {
    // 处理右上角颜色切换
    document.querySelectorAll('.colors label').forEach(item => {
      item.classList.remove('active')
    })
    e.target.parentNode.classList.add('active')
    document.querySelector('#color-text').innerText = colorsMap[e.target.id]

    // 中间图片 active 处理
    document.querySelectorAll('.iphone-images figure').forEach(item => {
      item.classList.remove('active')
    })
    let curImg = document.querySelector(`.iphone-images .${e.target.id}`)
    curImg.classList.add('active')
    // 给图片的父元素增加自定义属性值
    document.querySelector(`.iphone-images`).dataset.active = e.target.id
  }
})

3. Find X3 火星版官网动效实现

使用 GSAP 和 clip-path 实现 5 种场景切换动效

来源:OPPO Find X3 Pro 火星探索版 有色彩的地方 就有让生命感动的力量 | OPPO 官方网站

3-1-find-x3-s1-2.gif

1、首屏是一个 video 动画,没有加 loop,比较简单

2、第二屏也是一个 video 视频,加了鼠标样式,文字动画

修改鼠标样式代码如下

/*
<div class="section-2" style="width: 100%">
  <video src="xxx" muted autoplay loop style="width: 100%" ></video>
  <!-- @鼠标样式_start -->
  <div class="player-btn">
      <div>鼠标样式图片</div>
      <span class="text">观看完整视频</span>
  </div>
</div>
*/
let section2El = document.querySelector(".section-2");
let playBtnEl = document.querySelector(".player-btn");
section2El.addEventListener("click", (e) => {
  // alert("播放视频");
});
window.addEventListener(
  "mousemove",
  _.throttle((e) => {
    // 鼠标相对页面的位置
    let x = e.pageX;
    let y = e.pageY;
    // section-2 容器相对视口位置
    let parentX = section2El.offsetLeft;
    let parentY = section2El.offsetTop;
    let isYOut = y < parentY || y > parentY + section2El.clientHeight;
    let isXOut = x < parentX || x > parentX + section2El.clientWidth;
    if (isXOut || isYOut) {
      // console.log("移出去了");
      playBtnEl.style.opacity = "0";
      return;
    } else {
      playBtnEl.style.opacity = "1";
    }
    // 鼠标不在正中心,需要减去鼠标样式区域的宽高才能达到居中效果
    playBtnEl.style.left = `${x - parentX - playBtnEl.clientWidth / 2}px`;
    playBtnEl.style.top = `${y - parentY - playBtnEl.clientHeight / 2}px`;
  }, 50)
);

文字也有一个过渡动画,通过 clip-path 进行显示隐藏切换,再加上 transition 过渡

/* 初始化时,默认隐藏文字 */
.section-2-text .title {
  transition: all 0.7s; /* 当 css 属性发生变化时,0.7s 内完成变更 */
  /* 裁剪路径,inset 显示这个区域的内容,right、left 50%,会隐藏元素 */
  clip-path: inset(0 50% 0 50%);
  /*滚动到当前区域时,增加 class*/
}

通过 gsap 判断滚动距离,当这个区域滚动到视口中间(center)时,触发 class 添加。有 transition 就形成了动画

// 到视口中间时,父元素添加一个 fade-in 样式,显示该元素
.section-2.fade-in .title {
  clip-path: inset(0 0 0 0); /* 滚到到视口时,显示元素 */
}

// gsap 滚动处理
gsap.registerPlugin(ScrollTrigger);
gsap.to(".section-2-text", {
  opacity: 1,
  scrollTrigger: {
    trigger: ".section-2",
    start: "top center", // 当元素顶部部,滚动到达视口中间时, 开始动画
    // end 默认是 trigger 离开视口
    toggleClass: "fade-in",
    scrub: true, // 表示动画可以重复执行改成false表示只执行一次
    // markers: true, // 绘制开始位置和结束位置的线条
    // pin: false, // 动画执行期间,页面不进行滚动,动画执行结束后
  },
});

3、星空背景动画

3-2-find-x3-s3-4.gif

<canvas id="zn-starry-star" width="1440" height="969"></canvas>

代码为 canvas,通过 F12 查看 source,搜索 js 文件里面的 zn-starry-star 关键字,找到核心代码

查找前后代码,如下 https://www.oppo.com/content/dam/oppo/product-asset-library/find-x3-series/fussi-mars/v1/main-v3.js

关键字: vec4 星空动画 vec4 canvas 星空动画 vec4 canvas 星空动画

关键字: three.js 星空,搜索到一个比较接近的星空效果,地址:three.js 制作星空

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>3Dstar</title>
    <style>
      body {
        margin: 0;
        overflow: hidden;
      }
    </style>
    <script src="./threejs/three.min.js"></script
    <script src="./threejs/OrbitControls.js"></script>
    <!-- 
      建议保存到本地, 文件链接:
      https://cdn.bootcdn.net/ajax/libs/three.js/0.149.0/three.min.js
      http://nice.zuo11.com/3-find-x3-mars/threejs/OrbitControls.js 
     -->
  </head>
  <body>
    <script>
      /**
       * 创建场景对象Scene
       */
      var scene = new THREE.Scene();
      var intersectsArr = [];

      //星空背景
      var cloud = cloudFun();
      scene.add(cloud);

      function cloudFun() {
        var geom = new THREE.Geometry();
        var material = new THREE.ParticleBasicMaterial({
          size: 2,
          vertexColors: true,
        });
        var n = 1200;
        for (var i = 0; i < 3000; i++) {
          var particle = new THREE.Vector3(
            (Math.random() - 0.5) * n,
            (Math.random() - 0.5) * n,
            (Math.random() - 0.5) * n
          );
          geom.vertices.push(particle);
          let color_k = Math.random();
          // 蓝白色
          // geom.colors.push(new THREE.Color(color_k, color_k, color_k * 2.0));
          // 橙色为RGB为255,165,0,代码#FFA500
          geom.colors.push(new THREE.Color(color_k * 10, color_k * 3, color_k));
        }
        var cloud = new THREE.ParticleSystem(geom, material);
        return cloud;
      }

      /**
       * 透视投影相机设置
       */
      var width = window.innerWidth; //窗口宽度
      var height = window.innerHeight; //窗口高度
      /**透视投影相机对象*/
      var camera = new THREE.PerspectiveCamera(60, width / height, 1, 1000);
      camera.position.set(651, 613, 525); //设置相机位置
      camera.lookAt(scene.position); //设置相机方向(指向的场景对象)

      /**
       * 光源设置
       */
      //点光源
      var point = new THREE.PointLight(0xffffff);
      point.position.set(800, 200, 300);
      scene.add(point);
      // 点光源2  位置和point关于原点对称
      var point2 = new THREE.PointLight(0xffffff);
      point2.position.set(0, -500, 0); //点光源位置
      scene.add(point2); //点光源添加到场景中

      //环境光
      var ambient = new THREE.AmbientLight(0x000000);
      scene.add(ambient);

      /**
       * 创建渲染器对象
       */
      var renderer = new THREE.WebGLRenderer({
        antialias: true,
      });
      renderer.setSize(width, height); //设置渲染区域尺寸
      renderer.setClearColor(0x101010, 1); //设置背景颜色
      document.body.appendChild(renderer.domElement); //body元素中插入canvas对象

      let clock = new THREE.Clock();
      var FPS = 30;
      var refreshTime = 1 / FPS;
      var timeS = 0;
      function render() {
        var renderInterval = clock.getDelta();
        timeS = timeS + renderInterval;
        if (timeS > refreshTime) {
          //执行渲染操作
          renderer.render(scene, camera);
          timeS = 0;
        }
        //每次渲染位置变化,动态效果
        cloud.rotation.x += 0.0002;
        cloud.rotation.y += 0.0002;
        cloud.rotation.z += 0.0002;
        //周期性渲染
        requestAnimationFrame(render);
      }

      render();
      var controls = new THREE.OrbitControls(camera); //创建控件对象
    </script>
  </body>
</html>

three.js 星空在线预览

相关链接:

gsap 逻辑与 clip-path 逻辑

  • 内容分为 5 个部分,每个部分都是重叠的,初始都是隐藏的,opacity为 0, clip-path 左边裁切 100%(不显示)
  • 当滚动到对应的区域时,增加 active 属性,opacity 过渡到 1, clip-path 右裁切过渡到0 (不裁剪,完全显示)
  • 第一个动画结束后,onLeave 钩子里,开启下一个场景 active 添加。onEnter 钩子里面切换当前进度百分比显示
  • 整体的 pin 使用了父元素 pin,end 为滚动到 3000px 后,才结束固定。(屏幕适配可能会有问题,待优化)
<style>
.section-3-info .part img,
.left-desc .text-title,
.left-desc .text-detail {
  clip-path: inset(0 0 0 100%); /* 左侧裁剪 100% => 左侧裁剪 0%, 从右到左*/
  opacity: 0;
}
.section-3-info .part img {
  transition: clip-path 0.8s, opacity 0.8s;
}
.left-desc .text-title {
  transition: clip-path 0.4s, opacity 0.4s;
}
.left-desc .text-detail {
  transition: clip-path 0.4s 0.2s, opacity 0.6s;
}
.part.active .text-title, 
.part.active .text-detail, 
.part.active img {
  opacity: 1;
  clip-path: inset(0 0 0 0);
}
</style>
<script>
const changeProgress = (val) => {
  document.querySelector("#progress").innerHTML = `${val}%`;
};
let tl = gsap.timeline();
tl.to(".part-1", {
  opacity: 1,
  scrollTrigger: {
    trigger: ".part-1",
    start: "top top",
    end: "+1000",
    toggleClass: "active",
    scrub: true, // 表示动画可以重复执行改成false表示只执行一次
    //   markers: true, // 绘制开始位置和结束位置的线条
    onEnter: function () {
      changeProgress(33);
    },
    onEnterBack: function () {
      changeProgress(33);
    },
    onLeave: function () {
      gsap.to(".part-2", {
        opacity: 1,
        scrollTrigger: {
          trigger: ".part-2",
          start: "top top",
          end: "+1000",
          toggleClass: "active",
          scrub: true, // 表示动画可以重复执行改成false表示只执行一次
          // markers: true, // 绘制开始位置和结束位置的线条
          onEnter: function () {
            changeProgress(66);
          },
          onEnterBack: function () {
            changeProgress(66);
          },
          onLeave: function () {
            gsap.to(".part-3", {
              opacity: 1,
              scrollTrigger: {
                trigger: ".part-3",
                start: "top top",
                end: "+1000",
                toggleClass: "active",
                scrub: true, // 表示动画可以重复执行改成false表示只执行一次
                //   markers: true, // 绘制开始位置和结束位置的线条
                onEnter: function () {
                  changeProgress(100);
                },
                onEnterBack: function () {
                  changeProgress(100);
                },
                onLeave: () => {
                  // gsap.set('.part-2', {autoAlpha: 0});
                },
              },
            });
          },
        },
      });
    },
  },
});

// 固定容器
gsap.to(".section-3", {
  opacity: 1,
  scrollTrigger: {
    trigger: ".section-3",
    start: "top top", // 当元素顶部部,滚动到达视口中间时, 开始动画
    // end 默认是 trigger 离开视口
    end: "+3000", // 当section-2底部,到达可视区域bottom 500px时,结束动画
    // scrub: true, // 表示动画可以重复执行改成false表示只执行一次
    // markers: true, // 绘制开始位置和结束位置的线条
    pin: true, // 动画执行期间,页面不进行滚动,动画执行结束后
    onEnterBack() {
      document.querySelector('.abs.s-4-2').classList.remove('active')
      document.querySelector('.comp-inner').classList.remove('active')
    }
  },
});
</script>

小黄点定位 css animation

find-x3-mars-orange-dot-animation.gif

<style>
@keyframes circleScale {
  from {
    transform: scale(2);
  }
  to {
    transform: scale(0.5);
  }
}
@keyframes circleScaleSlow {
  from {
    transform: scale(1);
  }
  to {
    transform: scale(0.5);
  }
}
.circle-wrapper .fast-flow,
.circle-wrapper .slow-flow {
  width: 100%;
  height: 100%;
  position: absolute;
  opacity: 1;
  border: 1px solid #ff995e;
  background: transparent;
  border-radius: 50%;
}
.circle-wrapper .fast-flow {
  animation: circleScale 1.5s ease infinite;
}
.circle-wrapper .slow-flow {
  animation: circleScaleSlow 1.5s ease infinite;
}
/* 中心小黄点 */
.circle-wrapper .circle {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: #ff995e;
    margin-top: 50%;
    margin-left: 50%;
    transform: translate(calc(-50% + 1px), calc(-50% + 1px));
}
</style

<body>
  <!-- 所在位置 点 动画-->
  <div class="circle-wrapper abs circle1">
    <div class="fast-flow"></div>
    <div class="slow-flow"></div>
    <div class="circle"></div>
  </div>
</body>

其他场景代码参见: https://github.com/zuoxiaobai/nice-func/tree/main/src/3-find-x3-mars

3-3-find-x3-s5-6.gif

2. MacBook Pro 新品发布官网动画效果实现(2023年01月)

2-1-macbook-pro-video-1.gif

原始链接:MacBook Pro 14 英寸和 MacBook Pro 16 英寸 - Apple (**大陆)

Code

芯片滚动高光动画

2-3-macbook-pro-chip.gif

批量获取图片

// batch-index.js
// M2 pro 芯片切换动画 0000.jpg => 0052.png
// https://www.apple.com/105/media/us/macbook-pro-14-and-16/2022/1baf5961-c793-48e7-9efd-0d23cac1e101/anim/m2_pro/medium/medium_0051.jpg

// M2 max 芯片切换
// https://www.apple.com/105/media/us/macbook-pro-14-and-16/2022/1baf5961-c793-48e7-9efd-0d23cac1e101/anim/m2_max/medium/medium_0000.jpg

const axios = require('axios');
const fs = require('fs')
const path = require('path')

/**
 * 获取静态图片链接
 * @param {*} mode m2_pro 或 m2_max
 * @param {*} numStr '00' => '52'
 */
let getFileUrl = (mode, numStr) => `https://www.apple.com/105/media/us/macbook-pro-14-and-16/2022/1baf5961-c793-48e7-9efd-0d23cac1e101/anim/${mode}/medium/medium_00${numStr}.jpg`

const downloadImgFromUrl = (mode, fileUrl) => {
    axios.get(fileUrl, {
        responseType: 'arraybuffer'
    })
      .then(function (response) {
        // handle success
        let fileNameArr = fileUrl.split('/')
        let fileName = fileNameArr[fileNameArr.length - 1]
        fs.writeFileSync(path.resolve(__dirname, `./download/${mode}/${fileName}`), response.data)
        console.log('下载完成')
      })
      .catch(function (error) {
        // handle error
        console.log(error);
      })
      .finally(function () {
        // always executed
      });
}

for (let i = 0, len = 52; i <= len; i++) {
    let numStr = i + ''
    if (numStr.length < 2) {
        numStr = `0${numStr}` // 0 => '00', 9 => '09'
    }
    downloadImgFromUrl('m2_pro', getFileUrl('m2_pro', numStr))
    downloadImgFromUrl('m2_max', getFileUrl('m2_max', numStr))
}

canvas 播放帧动画,参考:滚动条控制播放的canvas逐帧动画

video 动画注意点:muted 才能 autoplay,循环播放视频 loop,如果不支持自动播视频有图片占位

<video src="./images/large.mp4" muted autoplay loop style="width: 100%;"></video>

视频文字遮罩,启停视频按钮

2-2-macbook-pro-video-mask.gif

<body>
  <div class="gray-mask" style="width: 100%; min-width: 800px;position: relative;">
      <video src="./images/medium.mp4" muted autoplay loop style="width: 100%;"></video>
      <div style="position: absolute;top: 30px;right:30px;color:#f5f5f7;font-size: 30px;">
          最高 19 核图形处理器
      </div>
      <div style="position: absolute;bottom: 100px;left:30px;color:#f5f5f7;font-size: 30px;">
          支持外接多达两台显示器
      </div>

      <div class="inline-media-ui" style="position: absolute;bottom: 22px;right: 22px;cursor: pointer;">
          <div class="play-pause-button" data-aria-playing="暂停野兽视频" style="width: 32px;height: 32px;;">
              <svg class="svg-icon pause-icon" version="1.1" xmlns="http://www.w3.org/2000/svg"
                  viewBox="0 0 85 85">
                  <path class="control-path circle-opaque" d="M42.5,2.5a40,40,0,1,1-40,40A40,40,0,0,1,42.5,2.5Z"
                      fill="none" stroke="#fff" stroke-linecap="round" stroke-width="4px" stroke-linejoin="round">
                  </path>
                  <path class="control-fill pause-line"
                      d="M50,28.25a2,2,0,0,1,2,2v24a2,2,0,1,1-4,0v-24A2,2,0,0,1,50,28.25Z"></path>
                  <path class="control-fill pause-line"
                      d="M35,28.25a2,2,0,0,1,2,2v24a2,2,0,1,1-4,0v-24A2,2,0,0,1,35,28.25Z"></path>
              </svg>
              <svg class="svg-icon play-icon" version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 85 85"
                  style="opacity: 1;z-index: 2;display: none">
                  <path class="control-path circle-opaque" d="M42.5,2.5a40,40,0,1,1-40,40A40,40,0,0,1,42.5,2.5Z"
                      fill="none" stroke="#fff" stroke-linecap="round" stroke-width="4px" stroke-linejoin="round">
                  </path>
                  <path class="control-fill play-shape"
                      d="M55.73,41.55a1.34,1.34,0,0,1,0,1.48,1.16,1.16,0,0,1-.52.52L35.27,56.1a1.33,1.33,0,0,1-.74.15,1.54,1.54,0,0,1-1.48-1.48v-25a1.25,1.25,0,0,1,.22-.74,1.16,1.16,0,0,1,.52-.52,1.25,1.25,0,0,1,.74-.22,2.19,2.19,0,0,1,.74.15L55.21,41a2,2,0,0,1,.52.59">
                  </path>
              </svg>
          </div>
      </div>
  </div>
  <script>
    /* 视频开始、暂停 */
    document.querySelector('.pause-icon').onclick = (e) => {
        document.querySelector('.gray-mask video').pause();
        document.querySelector('.pause-icon').style.display = 'none'
        document.querySelector('.play-icon').style.display = 'block'
    }
    document.querySelector('.play-icon').onclick = () => {
        document.querySelector('.gray-mask video').play();
        document.querySelector('.play-icon').style.display = 'none'
        document.querySelector('.pause-icon').style.display = 'block'
    }
  </script>
</body>

滑动切换按钮,纯 css,黑色背景很关键,不然效果出不来。核心代码如下:

2-4-macbook-pro-btn-switch.gif

<style>
/* 白色背景 */
.tabnav-item-1.current~.tabnav-highlight {
    transform: translateX(0%);
}
.tabnav-item-2.current~.tabnav-highlight {
    transform: translateX(100%);
}

/* 添加过渡效果 */
.tabnav-highlight {
  transition: .2s ease-out;
}
</style>

<script>
// 点击事件切换 .current
document.querySelectorAll('ul li').forEach(item => {
  item.onclick = (e) => {
    document.querySelectorAll('ul li').forEach(item => item.classList.remove('current') )
    item.classList.toggle('current')
  }
})
</script>

1. vivo iQOO Neo7 向下滚动切换手机颜色效果

vivo-scroll-switch-phone-color.gif

原始链接:iQOO Neo7 - vivo官方网站

Code

图片从上到下滚动时,位置切换

transform: translate(695px, 575px) scale(2.5, 2.5);
transform: translate3d(55.8193px, 46.1814px, 0px) scale(1.1205, 1.1205);
transform: translate(0px, 0px);

GSAP (GreenSock Animation Platform) https://github.com/greensock/GSAP

发音 [gi:s 'æp] 鸡丝哎婆

Getting Started with GSAP https://greensock.com/get-started/#creatingAnAnimation

gsap.fromTo(".phone-wrap img", { x: 695,y: 575, scale: 2.5,}, {x: 0, yy: 0, scale: 1});

scroll 滚动与 gsap 动画结合,使用 gasp ScrollTrigger 插件

Simple example

gsap.to(".box", {
  scrollTrigger: ".box", // start the animation when ".box" enters the viewport (once)
  x: 500
});

Advanced example

let tl = gsap.timeline({
    // yes, we can add it to an entire timeline!
    scrollTrigger: {
      trigger: ".container",
      pin: true,   // pin the trigger element while active
      start: "top top", // when the top of the trigger hits the top of the viewport
      end: "+=500", // end after scrolling 500px beyond the start
      scrub: 1, // smooth scrubbing, takes 1 second to "catch up" to the scrollbar
      snap: {
        snapTo: "labels", // snap to the closest label in the timeline
        duration: {min: 0.2, max: 3}, // the snap animation should be at least 0.2 seconds, but no more than 3 seconds (determined by velocity)
        delay: 0.2, // wait 0.2 seconds from the last scroll event before doing the snapping
        ease: "power1.inOut" // the ease of the snap animation ("power3" by default)
      }
    }
  });

// add animations and labels to the timeline
tl.addLabel("start")
  .from(".box p", {scale: 0.3, rotation:45, autoAlpha: 0})
  .addLabel("color")
  .from(".box", {backgroundColor: "#28a92b"})
  .addLabel("spin")
  .to(".box", {rotation: 360})
  .addLabel("end");

参考:使用GSAP实现滚动条交互动画

 scrollTrigger: {
    trigger: ".demo",
    /*
        * ⚠ start: 表示动画开始执行节点
        * ⚠ end: 表示动画执行结束
        * 参数1: 表示当前这个元素的开始执行动画的起点
        *      【0:就是当前这个元素的顶端位置】
        *      【正数:当前这个元素根据顶端位置进行向下偏移】
        *      【负数:当前这个元素根据顶端位置进行向上偏移】
        * 
        * 参数2: 表示当前滚动条的起点执行位置
        *      【0:浏览器最顶端】
        *      【正数:向下偏移】
        *      【负数:向上偏移】
        * 当两个位置重合时,动画开始执行/结束
        * 此处的位置可以使用像素单位,完全可以自定义
        */
    start: '0% 60%', 
    end: '100% 10%',
    markers: true, // 绘制开始位置和结束位置的线条
    scrub: true, // 表示动画可以重复执行改成false表示只执行一次
    pin: true // 动画执行期间,动画元素不跟随页面进行滚动,动画执行结束后,恢复滚动
}

nice-func's People

Contributors

dev-zuo avatar hubzy avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

nice-func's Issues

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.