-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
梁怀刚
committed
Jul 12, 2024
1 parent
df0a650
commit 2ab49b2
Showing
14 changed files
with
1,595 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
## Web端裁剪图片方法 | ||
|
||
|
||
|
||
|
||
由于在Web端,JavaScript不能直接处理本地文件,因此可以在后台裁剪图片,或者利用html5的canvas来处理。 | ||
|
||
### 方法1:传送到后台剪切 | ||
|
||
**步骤1:上传图片到后台,向前端返回图片URL** | ||
|
||
利用input标签,将文件发送到后台。 | ||
|
||
可以使用jQuery中的ajaxFileUpload方法 | ||
```javascript | ||
$.ajaxFileUpload( { | ||
url: 'live/apply/uploadImage', //用于文件上传的服务器端请求地址 | ||
type:'post', secureuri: false, //一般设置为false | ||
fileElementId: image, //文件上传空间的id属性 | ||
dataType: 'json', //返回值类型 一般设置为json | ||
data:data, //可以传递图片属性及其他数据 | ||
success: function (data, status) //服务器成功响应处理函数 { //上传传成功处理 }, | ||
error: function (data, status, e)//服务器响应失败处理函数 { //上传失败处理 } | ||
``` | ||
**步骤2: 进行裁剪,获取图片的坐标及长宽等值,传回后台** | ||
这里一般是利用一个移动的div来获取剪截的动画效果,目前有多种jquery插件可以使用,本文使用的是Jcrop插件,比较简单方便。 | ||
```javascript | ||
$("#myPhoto").Jcrop({ | ||
onChange: showPreview, | ||
onSelect: showPreview, | ||
aspectRatio: 1, | ||
}); | ||
function showPreview(coords) { | ||
if (parseInt(coords.w)) { | ||
//计算预览区域图片缩放的比例,通过计算显示区域的宽度(与高度)与剪裁的宽度(与高度)之比得到 | ||
var rx = $("#preview_box").width() / coords.w; | ||
var ry = $("#preview_box").height() / coords.h; | ||
//通过比例值控制图片的样式与显示 | ||
$("#crop_preview").css({ | ||
width: Math.round(rx * $("#myPhoto").width()) + "px", | ||
//预览图片宽度为计算比例值与原图片宽度的乘积 | ||
height: Math.round(rx * $("#myPhoto").height()) + "px", | ||
//预览图片高度为计算比例值与原图片高度的乘积 | ||
marginLeft: "-" + Math.round(rx * coords.x) + "px", | ||
marginTop: "-" + Math.round(ry * coords.y) + "px", | ||
}); | ||
$("#X1").val(coords.x); | ||
$("#Y1").val(coords.y); | ||
$("#X2").val(coords.x2); | ||
$("#Y2").val(coords.y2); | ||
$("#W").val(coords.w); | ||
$("#H").val(coords.h); | ||
} | ||
} | ||
``` | ||
根据上述过程,可以将获取到的剪截横纵坐标和长宽数据发送到后台。 | ||
**步骤3:后台裁剪图片** | ||
略 | ||
### 方法2:Html5的canvas技术 | ||
这个需要浏览器支持以下几个点,并且兼容性还没有进行测试: | ||
File API\ | ||
Blob\ | ||
canvas | ||
**步骤1:读取文件** | ||
如方法1一样,需要用input标签来获取file,但是JavaScript不能直接操作文件,因此需要File API来处理。 | ||
```javascript | ||
$("input[type=file]").change(function () { | ||
var file = this.files[0]; | ||
var reader = new FileReader(); | ||
reader.onload = function () { | ||
// 通过 reader.result 来访问生成的 DataURL | ||
var url = reader.result; | ||
setImageURL(url); | ||
}; | ||
reader.readAsDataURL(file); | ||
}); | ||
var image = new Image(); | ||
function setImageURL(url) { | ||
image.src = url; | ||
} | ||
|
||
``` | ||
**步骤2:获取裁剪坐标** | ||
参照方法1中的步骤2 | ||
**步骤3:利用canvas重绘图片** | ||
首先要设置剪截后的图片大小相等的canvas。 | ||
```javascript | ||
// 以下四个参数由步骤2获得 var x, y, width, height; | ||
var canvas = $(" ")[0], | ||
ctx = canvas.getContext("2d"); | ||
ctx.drawImage(image, x, y, width, height, 0, 0, width, height); //重绘 | ||
$(document.body).append(canvas); //添加到文档中可以查看效果 | ||
``` | ||
**步骤4:保存图片** | ||
我们要获取 canvas 中图片的信息,需要用 toDataURL 转换成上面用到的 DataURL 。 然后取出其中 base64 信息,再用window\.atob 转换成由二进制字符串。但 window\.atob 转换后的结果仍然是字符串,直接给 Blob 还是会出错。所以又要用Uint8Array 转换一下。 | ||
```javascript | ||
var data=canvas.toDataURL(); | ||
// dataURL 的格式为 “data:image/png;base64,\*\*\*\*”,逗号之前都是一些说明性的文字,我们只需要逗号之后的就行了 | ||
data=data.split(',')[1]; | ||
data=window.atob(data); | ||
var ia = new Uint8Array(data.length); for (var i = 0; i < data.length; i++) { ia[i] = data.charCodeAt(i); }; // canvas.toDataURL 返回的默认格式就是 image/png | ||
var blob=new Blob(\[ia], {type:"image/png"}); | ||
``` | ||
**步骤5:将blob数据发送至后台** | ||
在后台可以将Blob格式的数据转换成image保存。 | ||
> 原文标题:Web端裁剪图片方法\ | ||
> 原文链接:[https://segmentfault.com/a/1190000004268074](https://segmentfault.com/a/1190000004268074?ref=zhelin.me) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,205 @@ | ||
## jQuery实现弹幕效果、文字水平滚动/垂直滚动插件 | ||
|
||
|
||
<!--kg-card-begin: markdown--> | ||
|
||
在写这个插件之前,已经在很多专题页面需要用到弹幕,或者是文字滚动等效果,原理大致相同(也有细微区别),但一直没有封装成插件。也是在这些工作中尝试使用了多种方法,在这里也将一一列举其中的优缺点。 | ||
|
||
### **一、使用CSS Animation的解决方案** | ||
|
||
使用CSS Animation这个解决方案并不好,但这也是我最开始的一个思路。我个人认为这是一个最差的解决方案,在实际使用过程中发现了许多意外的问题。它的兼容性倒是其次,更大的问题是弹幕元素的不可控制,以及窗口失去焦点后,动画暂停,但是JS并没有暂停。这就导致了重新恢复窗口焦点后,弹幕一堆的出现。是一个非常不好的效果。 | ||
|
||
**思路:** | ||
|
||
使用@keyframes定义一个移动的动画,通过JS动态的加载弹幕元素到显示区域边缘,并指定相应的类。那么添加的元素就会执行我们定义的这个移动动画。那么这个弹幕的效果就实现了。当然这些元素都是绝对定位元素。 | ||
|
||
这个解决方案的思路大致就是这样,但其本身的局限性导致我不去使用它,就不拆开细说。 | ||
|
||
### 二、使用相对定位偏移的解决方案 | ||
|
||
使用这种方法较上一个方法效果好了很多,这个方法我之前都是用于列表滚动。并没有考虑用在弹幕这种效果上,那么该如何去实现呢? | ||
|
||
**思路:** | ||
|
||
这里的所说的相对定位并不是说设置元素的position值为relative。当然这样设置之后使用left/right/top/bottom来做偏移也是能实现这个效果的,但是在这里我是使用的margin来做偏移。效果与设置position值为relative一样。 | ||
|
||
**效果:** | ||
|
||
因为当时无法提供json数据,所以数据都是渲染到页面上的。 | ||
|
||
**JS:** | ||
|
||
``` | ||
var barrage = function (val) { | ||
this.box = $("#j-head-list .ui-repeater"); | ||
this.item = $("#j-head-list .ui-repeater .ui-repeater-item"); | ||
//保存第一个列表盒子 | ||
this.listBox1 = $("#j-head-list .list_01"); | ||
this.listBox2 = $("#j-head-list .list_02"); | ||
// 保存数据 | ||
this.data = []; | ||
// 需要暂停的元素 | ||
this.stopItem = null; | ||
// 首次渲染长度 | ||
this.firstLen = 10; | ||
this.init(); | ||
}; | ||
barrage.prototype = { | ||
init: function () { | ||
this.getData(); | ||
//this.news(); | ||
}, | ||
//获取并整理数据 | ||
getData: function () { | ||
var self = this; | ||
var item = $.makeArray(this.item); | ||
for (var i in item) { | ||
this.data.push(item[i].innerHTML.trim()); | ||
} | ||
this.data.forEach(function (item, index, array) { | ||
self.data[index] = item.replace(/\[tgc/g, "["); | ||
}); | ||
this.box.html(""); | ||
this.renderer(); | ||
}, | ||
//新闻列表整理 | ||
news: function () { | ||
var item = $(".news_list .ui-repeater").html().toString(); | ||
item = item.replace(/\[tgc/g, "["); | ||
$(".news_list .ui-repeater").html(item); | ||
}, | ||
//悬浮暂停 | ||
hoverStop: function () { | ||
var self = this; | ||
this.box.hover(function () { | ||
var $this = $(this); | ||
if ($this.hasClass("list_01")) { | ||
self.stopItem = 1; | ||
} else { | ||
self.stopItem = 2; | ||
} | ||
}, function () { | ||
self.stopItem = null; | ||
}) | ||
}, //渲染数据 | ||
renderer: function () { | ||
var self = this, start, dataLen = this.data.length; | ||
//第一次渲染数据 | ||
var index = 0; | ||
for (start = 0; start < this.firstLen; start++) { | ||
var _html = '<li class="ui-repeater-item" style="display: inline-block">' + this.data[index] + '</li>'; | ||
if (start % 2 == 0) { | ||
this.box.eq(0).append(_html); | ||
} else { | ||
this.box.eq(1).append(_html); | ||
} | ||
index++; | ||
if (dataLen < this.firstLen) { | ||
if (index == dataLen) { | ||
index = 0 | ||
} | ||
} | ||
} | ||
this.hoverStop(); | ||
//添加数据 | ||
var left1 = 0, left2 = 0, width1, width2; | ||
dataMonitor(); | ||
getWidth(); | ||
var time = setInterval(function () { | ||
//保存第一次宽度 | ||
if (left1 < width1 && self.stopItem != 1) { | ||
self.listBox1.css("margin-left", -(++left1) + 'px') | ||
} else if (left1 >= width1 && self.stopItem != 1) { | ||
var _html = '<li class="ui-repeater-item" style="display: inline-block">' + self.data[++start] + '</li>'; | ||
self.listBox1.children().eq(0).remove(); | ||
self.listBox1.css("margin-left", '-1px'); | ||
self.listBox1.append(_html); | ||
left1 = 1; | ||
dataMonitor(); | ||
getWidth(); | ||
} | ||
if (left2 < width2 && self.stopItem != 2) { | ||
self.listBox2.css("margin-left", -(++left2) + 'px') | ||
} else if (left2 >= width2 && self.stopItem != 2) { | ||
var _html = '<li class="ui-repeater-item" style="display: inline-block">' + self.data[++start] + '</li>'; | ||
self.listBox2.children().eq(0).remove(); | ||
self.listBox2.css("margin-left", '-1px'); | ||
self.listBox2.append(_html); | ||
left2 = 1; | ||
dataMonitor(); | ||
getWidth(); | ||
} | ||
}, 30); | ||
function getWidth() { | ||
width1 = self.box.eq(0).children().eq(0).css("width"); | ||
width2 = self.box.eq(1).children().eq(0).css("width"); | ||
width1 = parseInt(width1.substring(0, width1.indexOf('px'))) + 50; | ||
width2 = parseInt(width2.substring(0, width2.indexOf('px'))) + 50; | ||
} | ||
function dataMonitor() { | ||
if (start + 1 >= dataLen) { | ||
start = -1; | ||
} | ||
} | ||
} | ||
}; | ||
new barrage({}); | ||
``` | ||
|
||
从上面的代码可以看出,其实这个方法也是有瑕疵的,速度调节我们需要依赖setInterval的时间,或者是每执行一次时间循环元素的偏移量增大一点。但是无论怎么写,都会有滚动的顿挫感,不够平滑。那有什么方法来解决这些问题呢? | ||
|
||
### 三、基于requestAnimationFrame的解决方案 | ||
|
||
使用requestAnimationFrame应该是最好的一个解决方案了(就我的眼界来看),并结合translate3d() 做偏移,简直是完美。那么要使用requestAnimationFrame,我们就要知道什么事requestAnimationFrame,那么久简单的介绍一下: | ||
|
||
**requestAnimationFrame:** | ||
|
||
window\.requestAnimationFrame()这个方法是用来在页面重绘之前,通知浏览器调用一个指定的函数,以满足开发者操作动画的需求。这个方法接受一个函数为参,该函数会在重绘前调用。 | ||
|
||
如果你想做逐帧动画的时候,你应该用这个方法。这就要求你的动画函数执行会先于浏览器重绘动作。通常来说,被调用的频率是每秒60次,但是一般会遵循W3C标准规定的频率。如果是后台标签页面,重绘频率则会大大降低。 | ||
|
||
回调函数只会被传入一个DOMHighResTimeStamp参数,这个参数指示当前被 requestAnimationFrame 序列化的函数队列被触发的时间。因为很多个函数在这一帧被执行,所以每个函数都将被传入一个相同的时间戳,尽管经过了之前很多的计算工作。这个数值是一个小数,单位毫秒,精确度在 10 µs。 | ||
|
||
需要注意的是:如果想得到连贯的逐帧动画,函数中必须重新调用 requestAnimationFrame()。 | ||
|
||
**语法:** | ||
|
||
``` | ||
*requestID* = window.requestAnimationFrame(*callback*); // Firefox 23 / IE10 / Chrome / Safari 7 (incl. iOS) | ||
requestID = window.mozRequestAnimationFrame(*`callback`*); // Firefox < 23 | ||
requestID = window.webkitRequestAnimationFrame(callback); // Older versions Chrome/Webkit | ||
``` | ||
|
||
我们可以看到上面有兼容性写法,是的,其本身兼容性不太好,但是支持大部分的现代浏览器。但这不符合我们工作的需要,这我们不用担心,Opera浏览器的技术师Erik Möller 把这个函数进行了封装,使得它能更好的兼容各种浏览器。 | ||
|
||
``` | ||
(function () { | ||
var lastTime = 0; | ||
var vendors = ['webkit', 'moz']; | ||
for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { | ||
window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame']; | ||
window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] || window[vendors[x] + 'CancelRequestAnimationFrame']; | ||
} | ||
if (!window.requestAnimationFrame) window.requestAnimationFrame = function (callback, element) { | ||
var currTime = new Date().getTime(); | ||
var timeToCall = Math.max(0, 16 - (currTime - lastTime)); | ||
var id = window.setTimeout(function () { | ||
callback(currTime + timeToCall); | ||
}, timeToCall); | ||
lastTime = currTime + timeToCall; | ||
return id; | ||
}; | ||
if (!window.cancelAnimationFrame) window.cancelAnimationFrame = function (id) { | ||
clearTimeout(id); | ||
}; | ||
}()); | ||
``` | ||
|
||
这样,我们就可以放心的使用这个方法了。但仅限于现代浏览器。IE9+是没问题的。至于它的其他信息可以点击[这里查询](https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestAnimationFrame?ref=zhelin.me)。 | ||
|
||
**效果:** | ||
|
||
|
||
需要源码的同学可以点击[这里下载](https://github.com/ZhelinCheng/jquery.Barrage?ref=zhelin.me)! |
Oops, something went wrong.