【前端实例代码】用HTML、CSS和JavaScript创建一个简易图片编辑器(实现图片的亮度、饱和度、灰度、颜色反转、图片旋转镜面翻转等滤镜效果)

效果图:(知识点和完整代码在最后面)

 

 完整代码:

<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
    <meta charset="utf-8">
    <title>【前端实例代码】用HTML、CSS和JavaScript创建一个简易图片编辑器(实现图片的亮度、饱和度、灰度、颜色反转、图片旋转镜面翻转等滤镜效果)</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="https://unpkg.com/[email protected]/css/boxicons.min.css">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css"/>
    <style>
        /* Import Google font - Poppins */
        @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600&display=swap');

        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: 'Poppins', sans-serif;
        }

        body {
            display: flex;
            padding: 10px;
            min-height: 100vh;
            align-items: center;
            justify-content: center;
            background: #E3F2FD;
            background: linear-gradient(135deg, #4AB1FF, #2D5CFE);
        }

        .container {
            width: 850px;
            padding: 30px 35px 35px;
            background: #fff;
            border-radius: 10px;
            box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
        }

        .container.disable .editor-panel,
        .container.disable .controls .reset-filter,
        .container.disable .controls .save-img {
            opacity: 0.5;
            pointer-events: none;
        }

        .container h2 {
            margin-top: -8px;
            font-size: 22px;
            font-weight: 500;
        }

        .container .wrapper {
            display: flex;
            margin: 20px 0;
            min-height: 335px;
        }

        .wrapper .editor-panel {
            padding: 15px 20px;
            width: 280px;
            border-radius: 5px;
            border: 1px solid #ccc;
        }

        .editor-panel .title {
            display: block;
            font-size: 16px;
            margin-bottom: 12px;
        }

        .editor-panel .options, .controls {
            display: flex;
            flex-wrap: wrap;
            justify-content: space-between;
        }

        .editor-panel button {
            outline: none;
            height: 40px;
            font-size: 14px;
            color: #6C757D;
            background: #fff;
            border-radius: 3px;
            margin-bottom: 8px;
            border: 1px solid #aaa;
        }

        .editor-panel .filter button {
            width: calc(100% / 2 - 4px);
        }

        .editor-panel button:hover {
            background: #f5f5f5;
        }

        .filter button.active {
            color: #fff;
            border-color: #5372F0;
            background: #5372F0;
        }

        .filter .slider {
            margin-top: 12px;
        }

        .filter .slider .filter-info {
            display: flex;
            color: #464646;
            font-size: 14px;
            justify-content: space-between;
        }

        .filter .slider input {
            width: 100%;
            height: 5px;
            accent-color: #5372F0;
        }

        .editor-panel .rotate {
            margin-top: 17px;
        }

        .editor-panel .rotate button {
            display: flex;
            align-items: center;
            justify-content: center;
            width: calc(100% / 4 - 3px);
        }

        .rotate .options button:nth-child(3),
        .rotate .options button:nth-child(4) {
            font-size: 18px;
        }

        .rotate .options button:active {
            color: #fff;
            background: #5372F0;
            border-color: #5372F0;
        }

        .wrapper .preview-img {
            flex-grow: 1;
            display: flex;
            overflow: hidden;
            margin-left: 20px;
            border-radius: 5px;
            align-items: center;
            justify-content: center;
        }

        .preview-img img {
            max-width: 490px;
            max-height: 335px;
            width: 100%;
            height: 100%;
            object-fit: contain;
        }

        .controls button {
            padding: 11px 20px;
            font-size: 14px;
            border-radius: 3px;
            outline: none;
            color: #fff;
            cursor: pointer;
            background: none;
            transition: all 0.3s ease;
            text-transform: uppercase;
        }

        .controls .reset-filter {
            color: #6C757D;
            border: 1px solid #6C757D;
        }

        .controls .reset-filter:hover {
            color: #fff;
            background: #6C757D;
        }

        .controls .choose-img {
            background: #6C757D;
            border: 1px solid #6C757D;
        }

        .controls .save-img {
            margin-left: 5px;
            background: #5372F0;
            border: 1px solid #5372F0;
        }

        @media screen and (max-width: 760px) {
            .container {
                padding: 25px;
            }

            .container .wrapper {
                flex-wrap: wrap-reverse;
            }

            .wrapper .editor-panel {
                width: 100%;
            }

            .wrapper .preview-img {
                width: 100%;
                margin: 0 0 15px;
            }
        }

        @media screen and (max-width: 500px) {
            .controls button {
                width: 100%;
                margin-bottom: 10px;
            }

            .controls .row {
                width: 100%;
            }

            .controls .row .save-img {
                margin-left: 0px;
            }
        }
    </style>
</head>
<body>
<div class="container disable">
    <h2>简易图片编辑器</h2>
    <div class="wrapper">
        <div class="editor-panel">
            <div class="filter">
                <label class="title">滤镜</label>
                <div class="options">
                    <button id="brightness" class="active">亮度</button>
                    <button id="saturation">饱和度</button>
                    <button id="inversion">反转</button>
                    <button id="grayscale">灰度</button>
                </div>
                <div class="slider">
                    <div class="filter-info">
                        <p class="name">明亮度</p>
                        <p class="value">100%</p>
                    </div>
                    <input type="range" value="100" min="0" max="200">
                </div>
            </div>
            <div class="rotate">
                <label class="title">旋转和翻转</label>
                <div class="options">
                    <button id="left"><i class="fa-solid fa-rotate-left"></i></button>
                    <button id="right"><i class="fa-solid fa-rotate-right"></i></button>
                    <button id="horizontal"><i class='bx bx-reflect-vertical'></i></button>
                    <button id="vertical"><i class='bx bx-reflect-horizontal'></i></button>
                </div>
            </div>
        </div>
        <div class="preview-img">
            <img src="https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg" alt="preview-img">
        </div>
    </div>
    <div class="controls">
        <button class="reset-filter">重置</button>
        <div class="row">
            <input type="file" class="file-input" accept="image/*" hidden>
            <button class="choose-img">选择图片</button>
            <button class="save-img">保存图片</button>
        </div>
    </div>
</div>

<script>
    const fileInput = document.querySelector(".file-input"),
        filterOptions = document.querySelectorAll(".filter button"),
        filterName = document.querySelector(".filter-info .name"),
        filterValue = document.querySelector(".filter-info .value"),
        filterSlider = document.querySelector(".slider input"),
        rotateOptions = document.querySelectorAll(".rotate button"),
        previewImg = document.querySelector(".preview-img img"),
        resetFilterBtn = document.querySelector(".reset-filter"),
        chooseImgBtn = document.querySelector(".choose-img"),
        saveImgBtn = document.querySelector(".save-img");

    let brightness = "100", saturation = "100", inversion = "0", grayscale = "0";
    let rotate = 0, flipHorizontal = 1, flipVertical = 1;

    const loadImage = () => {
        let file = fileInput.files[0];
        if (!file) return;
        previewImg.src = URL.createObjectURL(file);
        previewImg.addEventListener("load", () => {
            resetFilterBtn.click();
            document.querySelector(".container").classList.remove("disable");
        });
    }

    const applyFilter = () => {
        previewImg.style.transform = `rotate(${rotate}deg) scale(${flipHorizontal}, ${flipVertical})`;
        previewImg.style.filter = `brightness(${brightness}%) saturate(${saturation}%) invert(${inversion}%) grayscale(${grayscale}%)`;
    }

    filterOptions.forEach(option => {
        option.addEventListener("click", () => {
            document.querySelector(".active").classList.remove("active");
            option.classList.add("active");
            filterName.innerText = option.innerText;

            if (option.id === "brightness") {
                filterSlider.max = "200";
                filterSlider.value = brightness;
                filterValue.innerText = `${brightness}%`;
            } else if (option.id === "saturation") {
                filterSlider.max = "200";
                filterSlider.value = saturation;
                filterValue.innerText = `${saturation}%`
            } else if (option.id === "inversion") {
                filterSlider.max = "100";
                filterSlider.value = inversion;
                filterValue.innerText = `${inversion}%`;
            } else {
                filterSlider.max = "100";
                filterSlider.value = grayscale;
                filterValue.innerText = `${grayscale}%`;
            }
        });
    });

    const updateFilter = () => {
        filterValue.innerText = `${filterSlider.value}%`;
        const selectedFilter = document.querySelector(".filter .active");

        if (selectedFilter.id === "brightness") {
            brightness = filterSlider.value;
        } else if (selectedFilter.id === "saturation") {
            saturation = filterSlider.value;
        } else if (selectedFilter.id === "inversion") {
            inversion = filterSlider.value;
        } else {
            grayscale = filterSlider.value;
        }
        applyFilter();
    }

    rotateOptions.forEach(option => {
        option.addEventListener("click", () => {
            if (option.id === "left") {
                rotate -= 90;
            } else if (option.id === "right") {
                rotate += 90;
            } else if (option.id === "horizontal") {
                flipHorizontal = flipHorizontal === 1 ? -1 : 1;
            } else {
                flipVertical = flipVertical === 1 ? -1 : 1;
            }
            applyFilter();
        });
    });

    const resetFilter = () => {
        brightness = "100";
        saturation = "100";
        inversion = "0";
        grayscale = "0";
        rotate = 0;
        flipHorizontal = 1;
        flipVertical = 1;
        filterOptions[0].click();
        applyFilter();
    }

    const saveImage = () => {
        const canvas = document.createElement("canvas");
        const ctx = canvas.getContext("2d");
        canvas.width = previewImg.naturalWidth;
        canvas.height = previewImg.naturalHeight;

        ctx.filter = `brightness(${brightness}%) saturate(${saturation}%) invert(${inversion}%) grayscale(${grayscale}%)`;
        ctx.translate(canvas.width / 2, canvas.height / 2);
        if (rotate !== 0) {
            ctx.rotate(rotate * Math.PI / 180);
        }
        ctx.scale(flipHorizontal, flipVertical);
        ctx.drawImage(previewImg, -canvas.width / 2, -canvas.height / 2, canvas.width, canvas.height);

        const link = document.createElement("a");
        link.download = "image.jpg";
        link.href = canvas.toDataURL();
        link.click();
    }

    filterSlider.addEventListener("input", updateFilter);
    resetFilterBtn.addEventListener("click", resetFilter);
    saveImgBtn.addEventListener("click", saveImage);
    fileInput.addEventListener("change", loadImage);
    chooseImgBtn.addEventListener("click", () => fileInput.click());
</script>

</body>
</html>

知识点:

 涉及到的知识点:

1、HTML DOM querySelector() 方法

querySelector() 方法返回文档中匹配指定 CSS 选择器的一个元素。

注意: querySelector() 方法仅仅返回匹配指定选择器的第一个元素。如果你需要返回所有的元素,请使用 querySelectorAll() 方法替代。

语法

document.querySelector(CSS selectors)

参数值

参数 类型 描述
CSS 选择器 String 必须。指定一个或多个匹配元素的 CSS 选择器。 可以使用它们的 id, 类, 类型, 属性, 属性值等来选取元素。

对于多个选择器,使用逗号隔开,返回一个匹配的元素。

提示: 更多 CSS 选择器,请参阅我们的 CSS 选择器参考手册

技术细节

DOM 版本: Selectors Level 1 Document Object
返回值: 匹配指定 CSS 选择器的第一个元素。 如果没有找到,返回 null。如果指定了非法选择器则 抛出 SYNTAX_ERR 异常。

推荐阅读:

HTML DOM querySelector() 方法 | 菜鸟教程HTML DOM querySelector() 方法 Document 对象 实例 获取文档中 id='demo' 的元素: document.querySelector('#demo'); 尝试一下 » 定义和用法 querySelector() 方法返回文档中匹配指定 CSS 选择器的一个元素。 注意: querySelector() 方法仅仅返回匹配指定选择器的第一个元..https://www.runoob.com/jsref/met-document-queryselector.html

2、HTML DOM addEventListener() 方法

定义和用法

addEventListener() 方法用于向指定元素添加事件句柄。

提示: 使用 removeEventListener() 方法来移除 addEventListener() 方法添加的事件句柄。

语法

element.addEventListener(eventfunctionuseCapture)

参数值

参数 描述
event 必须。字符串,指定事件名。

注意: 不要使用 "on" 前缀。 例如,使用 "click" ,而不是使用 "onclick"。

提示: 所有 HTML DOM 事件,可以查看我们完整的 HTML DOM Event 对象参考手册
function 必须。指定要事件触发时执行的函数。

当事件对象会作为第一个参数传入函数。 事件对象的类型取决于特定的事件。例如, "click" 事件属于 MouseEvent(鼠标事件) 对象。
useCapture 可选。布尔值,指定事件是否在捕获或冒泡阶段执行。

可能值:
  • true - 事件句柄在捕获阶段执行
  • false- false- 默认。事件句柄在冒泡阶段执行

推荐阅读:

HTML DOM addEventListener() 方法 | 菜鸟教程HTML DOM addEventListener() 方法 元素对象 实例 为 <button> 元素添加点击事件。 当用户点击按钮时,在 id='demo' 的 <p> 元素上输出 'Hello World' : document.getElementById('myBtn').addEventListe..https://www.runoob.com/jsref/met-element-addeventlistener.html

3、HTML DOM classList 属性

定义和用法

classList 属性返回元素的类名,作为 DOMTokenList 对象。

该属性用于在元素中添加,移除及切换 CSS 类。

classList 属性是只读的,但你可以使用 add() 和 remove() 方法修改它。

语法

element.classList

Properties

属性 Description
length 返回类列表中类的数量

该属性是只读的

推荐阅读:

HTML DOM classList 属性 | 菜鸟教程HTML DOM classList 属性 元素对象 实例 为 <div> 元素添加 class: document.getElementById('myDIV').classList.add('mystyle'); 尝试一下 » 定义和用法 classList 属性返回元素的类名,作为 DOMTokenList 对象。 该属性用于在元素中添加,移除..https://www.runoob.com/jsref/prop-element-classlist.html

4、HTML DOM innerHTML 属性

定义和用法

innerHTML 属性设置或返回表格行的开始和结束标签之间的 HTML。

语法

HTMLElementObject.innerHTML=text

4、forEach() 方法

5、map() 方法

6、filter()方法

7、join()方法

8、toLowerCase()方法

9、es6模板字符串

待补充

猜你喜欢

转载自blog.csdn.net/qq_22182989/article/details/126181442