CSS+ JS 实现手电筒效果

Posted 记得抬头微笑

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CSS+ JS 实现手电筒效果相关的知识,希望对你有一定的参考价值。

前言概述

javascript 结合 CSS 打造的一款图片特效,当鼠标拖拽滑块时,让本该置灰的图片局部恢复本来的颜色。且该效果随着你的鼠标的按下时的移动而移动。

核心功能

  • 图片置灰
  • 拖拽功能
  • 让滑块位置处的图片恢复本来的颜色

实现原理

这个的实现原理并不复杂,我们只需要一张背景图作为我们的素材,再单独定义一个滑块让并使用背景图裁剪的方式动态去显示图片的不同位置。

实现手电筒的框架

搭建框架

要实现这个功能,首先需要根据结构搭建一个样式结构出来

// css
/* 外层容器 */
.container 
    position: relative;
    width: 500px;
    height: 300px;


/* 图片 */
.baImg 
    display: block;
    width: 100%;
    height: 100%;
    filter: grayscale(100%);
    user-select: none;
    /* 阻止图片被拖拽 */
    -webkit-user-drag: none;


/* 手电筒 */
.move 
    position: absolute;
    width: 100px;
    height: 100px;
    top: 0;
    left: 0;
    border-radius: 50%;
    background: url("./1.png") no-repeat;
    background-size: 500px 300px;
    background-position: 0 0;
    /* 增加盒子阴影 */
    box-shadow: 0 0 5px 5px #fff; 

// html
<div class="container">
    <!-- 背景图 -->
    <img class="baImg" src="./1.png" alt="">
    <!-- 手电筒 -->
    <div class="move"></div>
</div>

!! -webkit-user-drag: none; 是 CSS 控制一个元素和它的内容是否可以被拖拽

取值

auto:使用默认的拖拽行为,这种情况只有图片和链接可以被拖拽。
element:整个元素(不仅仅只是它的内容)可拖拽。
none:元素不能被拖动。在通过选中后可拖拽。

filter: grayscale(100%); 是 CSS 用来定义元素(通常是图片)的视觉滤镜效果

grayscale: 将图片转换为灰阶,数值的大小表示转换的程度,区间在 0 - 100
更多参数可以参考 菜鸟教程- filter 的使用

根据上述的样式和结构搭建出来的结果,它就长下图这个样子

逻辑处理

首先我们需要给手电筒增加拖拽功能,在拖拽过程中动态获取鼠标的移动距离,然后更新手电筒本身的位置,并使用 background-clip 背景图裁剪的方式将原图的部分区域裁剪到手电筒的容器内显示原图,这样基本上就可以实现手电筒的效果了。

  1. 获取外层容器和手电筒的元素,并用容器的大小减去手电筒本身的大小
  2. 给手电筒增加拖拽效果,根据上述计算出来的大小限制拖拽范围
  3. 拖拽过程中得到拖拽的位置,更新手电筒本身的位置、背景图裁剪的位置
// 获取最外层的容器
let container = document.getElementsByClassName('container')[0];
// 获取手电筒
let move = document.getElementsByClassName('move')[0];

// 容器的大小减去移动目标的大小得到,X、Y 拖拽距离上的最大拖拽范围
let maxWidth = container.clientWidth - move.clientWidth;
let maxHeight = container.clientHeight - move.clientHeight;
move.onmousedown = function (moveStart) 
    // 获取鼠标在元素身上点下的位置
    let left = moveStart.offsetX;
    let top = moveStart.offsetY;
    document.onmousemove = function (ev) 
        // 获取拖拽后鼠标距离可视区的位置
        let  clientX, clientY  = ev;
        let _cX = clientX - left;
        let _cY = clientY - top;
        // 限制手电筒的移动范围
        if (_cX <= 0) 
            _cX = 0;
         else if (_cX >= maxWidth) 
            _cX = maxWidth;
        
        if (_cY <= 0) 
            _cY = 0;
         else if (_cY >= maxHeight) 
            _cY = maxHeight;
        
        move.style.left = (_cX) + 'px';
        move.style.top = (_cY) + 'px';
        // !!!! 这里的背景图裁剪一定要是负的,如果是正数就会出现问题
        move.style.backgroundPosition = `-$_cXpx -$_cYpx`;
    
    // 在拖拽结束后,将绑定在 document 身上的事件删除,避免重复绑定和触发
    document.onmouseup = function () 
        console.log('关闭');
        document.onmousemove = null;
        document.onmouseup = null;
    

实现效果

在手电筒上按下拖拽移动的时候,就可以看到在手电筒的区域显示了原图本来的色彩

总结

到这里看一下我们都一起实现文章开头说的这些功能

  • 图片置灰
  • 拖拽功能
  • 让滑块位置处的图片恢复本来的颜色

完整代码

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>手电筒</title>
    <style>
        /* 外层容器 */
        .container 
            position: relative;
            width: 500px;
            height: 300px;
        

        /* 图片 */
        .baImg 
            display: block;
            width: 100%;
            height: 100%;
            filter: grayscale(100%);
            user-select: none;
            /* 阻止图片被拖拽 */
            -webkit-user-drag: none;
        

        /* 手电筒 */
        .move 
            position: absolute;
            width: 100px;
            height: 100px;
            top: 0;
            left: 0;
            border-radius: 50%;
            background: url("./1.png") no-repeat;
            background-size: 500px 300px;
            background-position: 0 0;
            /* 增加盒子阴影 */
            box-shadow: 0 0 5px 5px #fff; 
        
    </style>
</head>

<body>
    <div class="container">
        <!-- 背景图 -->
        <img class="baImg" src="./1.png" alt="">
        <!-- 手电筒 -->
        <div class="move"></div>
    </div>
    <script>
        // 获取最外层的容器
        let container = document.getElementsByClassName('container')[0];
        // 获取手电筒
        let move = document.getElementsByClassName('move')[0];

        // 容器的大小减去移动目标的大小得到,X、Y 拖拽距离上的最大拖拽范围
        let maxWidth = container.clientWidth - move.clientWidth;
        let maxHeight = container.clientHeight - move.clientHeight;
        move.onmousedown = function (moveStart) 
            // 获取鼠标在元素身上点下的位置
            let left = moveStart.offsetX;
            let top = moveStart.offsetY;
            document.onmousemove = function (ev) 
                // 获取拖拽后鼠标距离可视区的位置
                let  clientX, clientY  = ev;

                let _cX = clientX - left;
                let _cY = clientY - top;
                // 限制手电筒的移动范围
                if (_cX <= 0) 
                    _cX = 0;
                 else if (_cX >= maxWidth) 
                    _cX = maxWidth;
                
                if (_cY <= 0) 
                    _cY = 0;
                 else if (_cY >= maxHeight) 
                    _cY = maxHeight;
                
                move.style.left = (_cX) + 'px';
                move.style.top = (_cY) + 'px';
                // !!!! 这里的背景图裁剪一定要是负的,如果是正数就会出现问题
                move.style.backgroundPosition = `-$_cXpx -$_cYpx`;
            
            // 在拖拽结束后,将绑定在 document 身上的事件删除,避免重复绑定和触发
            document.onmouseup = function () 
                console.log('关闭');
                document.onmousemove = null;
                document.onmouseup = null;
            
        

    </script>
</body>

</html>

本篇实现手电筒效果就到此结束了,这个效果用到的场景比较少,如果读者有更好的思路或者实现效果的话,欢迎在评论区活跃讨论。文章中的案例可以正常使用哦 ~

关于Unity中红外线瞄准的效果实现

今天做一个FPS游戏的时候,由于我做的是第三人称的射击,所以需要一个枪的红外线瞄准的效果。

一开始我在枪上挂一个很细很长的聚光灯,瞄准远处物体的时候,看起来有点红外线的样子,但是靠近之后光线就变成一个手电筒的那种光,不是我想要的效果。

后来我用粒子特效,虽然远处近处都是一条射线,但是效果很粗糙,不是那种细细的有穿透的感觉,而是像激光一样,而且感觉不断生成粒子,性能消耗会很大。

最后在网上看到有人用Unity3.5自带的例子AngryBots里面有红外瞄准效果,但是代码有点问题,就改了一下,感觉效果还可以。

 

 

红外线瞄准的效果实现:

1.创建一个空节点叫Laser,添加Line Renderer组件,里面关联一个材质,材质使用AngryBots里面的LaserMaterial.mat,材质纹理贴图是LaserTexture.psd和SimpleNoise.psd。Shader使用AngryBots里面的LaserScope.shader。这个是射线。

2.创建一个平面叫LaserDot作为Laser节点的子节点,Scale都设置为0.02,材质使用AngryBots里面的LaserDot.mat,材质纹理贴图是LaserDot.psd和SimpleNoise.psd。Shader使用AngryBots里面的LaserScope.shader。这个是射到物体表面时的一个红色的点。

3.创建一个脚本组件PreFrameRaycast挂载在Laser下面,用来发射一条射线,并返回射线的碰撞信息

using UnityEngine;
using System.Collections;
//转载请说明出处

public class PreFrameRaycast : MonoBehaviour
{
    private RaycastHit hitInfo;
    private Transform tr;
    // Use this for initialization
    void Start()
    {

    }

    void Awake()
    {
        tr = this.transform;
    }

    // Update is called once per frame
    void Update()
    {
        hitInfo = new RaycastHit();
        Physics.Raycast(tr.position, tr.forward, out hitInfo);
        Debug.DrawRay(tr.position, tr.forward, Color.red);
    }
    //返回射线的碰撞信息
    public RaycastHit GetHitInfo()
    {
        if (hitInfo.Equals(null))
        {
            Debug.LogWarning("hitInfo is null");
        }
        return hitInfo;
    }
}

4.创建一个脚本组件pointerCtrl挂载在Laser下面,用来绘制红外射线

 打开pointerCtrl.cs

using UnityEngine;
using System.Collections;
//转载请说明出处
public class pointerCtrl : MonoBehaviour
{
    public float scrollSpeed = 0.5f;
    public float pulseSpeed = 1.5f;
    public float noiseSize = 1.0f;
    public float maxWidth = 0.5f;
    public float minWidth = 0.5f;
    private float aniTime = 0.0f;
    private float aniDir = 1.0f;
    private LineRenderer lRenderer;
    public GameObject pointer = null;  //小红点
    private PreFrameRaycast raycast;   //光线投射

    void Start()
    {
        lRenderer = gameObject.GetComponent (typeof(LineRenderer)) as LineRenderer;
        raycast = gameObject.GetComponent(typeof(PreFrameRaycast)) as PreFrameRaycast;// Update is called once per frame
    }
    void Update()
    {
        //光线看起来有动感
        GetComponent<Renderer>().material.mainTextureOffset += new Vector2(Time.deltaTime * aniDir * scrollSpeed, 0);
        //设置纹理偏移量
        GetComponent<Renderer>().material.SetTextureOffset("_NoiseTex", new Vector2(-Time.time * aniDir * scrollSpeed, 0.0f));

        float aniFactor = Mathf.PingPong(Time.time * pulseSpeed, 1.0f);
        aniFactor = Mathf.Max(minWidth, aniFactor) * maxWidth;
        //设置光线的宽
        lRenderer.SetWidth(aniFactor, aniFactor);
        //光线的起点,枪口的地方
        lRenderer.SetPosition(0, this.gameObject.transform.position);
        if (raycast == null)
        {
            Debug.Log("raycast is null");
            return;
        }
        //获取光线的碰撞信息
        RaycastHit hitInfo = raycast.GetHitInfo();
        //光线碰撞到物体
        if (hitInfo.transform)
        {
            //光线的终点,即光线的碰撞点
            lRenderer.SetPosition(1, hitInfo.point);
            GetComponent<Renderer>().material.mainTextureScale = new Vector2(0.1f * (hitInfo.distance), GetComponent<Renderer>().material.mainTextureScale.y);
            GetComponent<Renderer>().material.SetTextureScale("_NoiseTex", new Vector2(0.1f * hitInfo.distance * noiseSize, noiseSize));

            if (pointer)
            {
                pointer.GetComponent<Renderer>().enabled = true;
                //pointer.transform.position = hitInfo.point + (transform.position - hitInfo.point) * 0.01f;
                pointer.transform.position = hitInfo.point;
                pointer.transform.rotation = Quaternion.LookRotation(hitInfo.normal, transform.up);
                pointer.transform.eulerAngles = new Vector3(90, pointer.transform.eulerAngles.y, pointer.transform.eulerAngles.z);
            }
        }
        else
        {    //光线没有碰撞到物体
            if (pointer)
            {
                pointer.GetComponent<Renderer>().enabled = false;
            }
            //光线的最大长度
            float maxDist = 200.0f;
            //当光线没有碰撞到物体,终点就是枪口前方最大距离处
            lRenderer.SetPosition(1, (this.transform.forward * maxDist));
            GetComponent<Renderer>().material.mainTextureScale = new Vector2(0.1f * maxDist, GetComponent<Renderer>().material.mainTextureScale.y);
            GetComponent<Renderer>().material.SetTextureScale("_NoiseTex", new Vector2(0.1f * maxDist * noiseSize, noiseSize));
        }
    }
}

5.把这个Laser节点挂在枪的指定位置作为子节点,运行的效果

 

以上是关于CSS+ JS 实现手电筒效果的主要内容,如果未能解决你的问题,请参考以下文章

如何用css,js实现如图效果

JS实现css的hover效果,兼容移动端

js+css3实现旋转效果

js+css在IE下点击滑动效果一点击就自动跳动

Animate.css+js实现鼠标经过动画效果

js+css+html实现固定侧边栏效果