公司的项目中需要对数据做可视化处理,高级点的D3.js目前还没接触到,因此选用了大众化的Echarts, 在vue的生态系统中已经有实现好的vue-echarts,但是使用现成的就意味着必须使用它定制好的数据结构,我也没办法对他进行一些修改。我个人也偏向于原生JS编程,因此没有采用,而是自己在vue中实现了对数据的可视化处理,先来看看效果图
这是目前用到的三种图。
可以看到,我在图表的外部添加了标题及说明,以及右侧的选择框组件,视图可以根据选择的不同,图表进行动态切换(echarts也是数据驱动),这就是个人定制化的好处
所有的数据都是动态获取的,由前端向后台请求。当然请求数据肯定不会放在图表组件中,而是放在了外部。因为架构设计的不合理(MD前端就我一个人!),因此前期获取数据及存取数据的方式,和后期也较大的不同。
这么大型的项目,vuex少不了。在前面的组件中,一次请求数据,然后将数据存储到了vuex中,echarts组件再从vuex中获取数据。这样的做法可能代码要稍微复杂点,但是数据存储在vuex中是随时可见的,我们也能随时保存获取的结果,对这些数据可以添加收藏也可以加入缓存中,下次再请求可以先从缓存调用。
(然而这只是我前端的想法,后台已经实现了对请求数据的缓存)
再对数据进行还原的时候(我的收藏,最近浏览),因为不需要保存或者收藏数据,我就直接用一个父组件请求,然后再分发到echarts组件,这样没有经过vuex,代码想多要少些。
<template> <div class="r-echarts-line"> <div class="top"> <div class="title"> {{origin.title}} </div> <div class="select-list"> <Select style="width:120px;margin-right:.5rem" v-model="pagePick"> <Option v-for="item in origin.page_select" :key="item" :value="item.val">{{item.name}}</Option> </Select> <Select style="width:120px" v-model="typePick"> <Option v-for="item in typeList" :value="item.name" :key="item">{{item.name}}</Option> </Select> </div> </div> <div class="des">说明:符合于本次筛选条件的共有<span class='tips'>{{origin.desc}}</span>条<span style="font-weight:700;color:black">职位信息</span>。</div> <div class="bottom" id="echart" ref="mychart"> </div> </div></template>
这是组件的html部分,可以看见top以及des是我自己添加的,bottom才是核心,也是整个echarts展示的部分,注意这里添加了ref,在script的代码中,我们将通过这个钩子,将DOM挂载到echarts中
<script>// echarts相关let echarts = require('echarts/lib/echarts');require('echarts/lib/chart/bar');require('echarts/lib/component/tooltip');require('echarts/lib/component/toolbox');require('echarts/lib/component/legend');require('echarts/lib/component/markLine');export default { name: 'r-echarts-line', data () { return { typePick: '数值', typeList: [ { name: '数值' }, { name: '百分比' } ], pagePick: 0, // myChart实例 myChart: {}, percent: { label: { normal: { show: true, position: 'inside', formatter: '{c}%' } } }, numeric: { label: { normal: { show: true, position: 'inside', formatter: '{c}' } } } } }, props: { index: { required: true, type: Number }, data: { required: true, type: Object } }, mounted () { this.setEchart(); }, updated () { if (!this.myChart) { this.setEchart(); } this.chartChange(); }, computed: { origin () { return this.data; }, opt() { let that = this; let obj = { color: ['#606c94'], tooltip: { }, toolbox: { show: true, feature: { saveAsImage: {show: true} } }, label: { normal: { show: true, position: 'inside', formatter: '{c}' }, emphasis: { show: true } }, xAxis: { type: 'value', }, yAxis: { data: that.origin[that.type][that.pagePick].key, axisLabel: { interval: 0, rotate: -30 } }, series: [{ name: that.origin.title, type: 'bar', data: that.origin[that.type][that.pagePick].val, barMaxWidth: '30', markLine: { data: [ {type: 'average', name: '平均值'} ] } }] } return obj; }, type () { if (this.typePick == '数值') { return 'numeric'; } else if (this.typePick == '百分比') { return 'percent'; } else { return 'numeric'; } } }, methods: { setEchart () { let dom = this.$refs.mychart; this.myChart = echarts.init(dom); this.myChart.setOption(this.opt); }, chartChange () { this.myChart.setOption(this.opt); if (this.typePick == '百分比') { this.myChart.setOption(this.percent); } if (this.typePick == '数值') { this.myChart.setOption(this.numeric); } } }}</script>
首先我引入了需要的echarts组件,这个部分通过npm i echarts -S添加。
接着data部分我设置了那些将会引起变化的参数。需要注意的是,我并没有将echarts的opt部分写入到data中,因为整个图表是基于数据驱动的,并且随时会发生变化,因此我将opt设置为计算属性computed,这样opt将会根据我的选择动态变化,echarts也将动态响应,mychart用于接收echarts生成的图表实例,再参数变换的时候将会起作用。
props部分是我接收到的参数,这个组件时基于前面我讲的第二种方式——父组件获取数据分发,data是父组件分发给echarts的数据源。
暂时忽略两个Vue生命周期钩子, 后面讲
计算属性中设置了两个属性,origin和opt,注意这个origin很重要,通过它我将opt项与复杂的数据解耦,无论外面的数据怎么换,opt只关心origin的值,而这个opt在两种数据获取的方式中是不一样的,使用vuex的方式,origin将会直接从vuex中获取数据。这样一定程度上也实现了组件的复用。
opt就是该图表组件的设置项了,这个参数按照官网给的配置,自己搭配即可。
接下来是methods部分,setEchart将会完成对整个图表的初始化,通过this.$refs获取DOM实例,再由echars生成实例并绑定在data中的mychart选项。
chartChange是用来响应我自定义组件的变化的,针对选框的不同将会有不同的显示情况。在这里是百分比和数据的切换
接着是前面忽略的生命周期部分
mounted里使用setEchart方法,初始化图表组件,一定要在这里使用该方法,否则会找不到DOM
updated周期里是响应参数变化的方法,首先检测该实例有没有生成(单页应用因为用户可能存在的误操作,很可能导致实例没有生成,这里检测是很有必要的),接着在vue中的数据发生改变时运行chartChange方法,注意,我的选择框是没有绑定事件的,只是通过v-model改变了参数,然后opt动态响应了参数的变化。当opt的参数变化的时候,updated中的方法就会执行,echarts也会动态响应。这个就是使用基于数据驱动vue最精巧的地方,避免了通过事件调用echartChange方法。也是vue中使用echarts核心的一环
另外还有一个就是获取地图参数的,并不用在官网里下载,提供的npm包里就有,按需引用就好了(使用官网的js版本会报错没找到echarts)
import echarts from 'echarts/lib/echarts';import 'echarts/lib/chart/map';import 'echarts/map/js/china.js';
style部分就没什么好聊的了,只需要记住一点,必须显式指定加载echarts 的DOM的宽度和高度
调用组件的方法按照常规组件调用就好了,只是因为echarts加载数据绘制需要耗费不少时间,我们可能需要通过keep-alive保存组件在内存中,避免切出去的时候被释放了。另外可能一个页面需要展示多个echarts类型组件,这里考虑使用component动态组件
<template> <div class="focus-echarts-wrap"> <keep-alive> <component :is="currentView" :index="focusType"></component> </keep-alive> </div></template>
Java 8 推出了全新的日期时间API,在教程中我们将通过一些简单的实例来学习如何使用新API。
Java处理日期、日历和时间的方式一直为社区所诟病,将 java.util.Date设定为可变类型,以及SimpleDateFormat的非线程安全使其应用非常受限。
新API基于ISO标准日历系统,java.time包下的所有类都是不可变类型而且线程安全。
Java 8 中的 LocalDate 用于表示当天日期。和java.util.Date不同,它只有日期,不包含时间。当你仅需要表示日期时就用这个类。
package com.shxt.demo02; import java.time.LocalDate; public class Demo01 { public static void main(String[] args) { LocalDate today = LocalDate.now(); System.out.println("今天的日期:"+today); } }
package com.shxt.demo02; import java.time.LocalDate; public class Demo02 { public static void main(String[] args) { LocalDate today = LocalDate.now(); int year = today.getYear(); int month = today.getMonthValue(); int day = today.getDayOfMonth(); System.out.println("year:"+year); System.out.println("month:"+month); System.out.println("day:"+day); } }
我们通过静态工厂方法now()非常容易地创建了当天日期,你还可以调用另一个有用的工厂方法LocalDate.of()创建任意日期, 该方法需要传入年、月、日做参数,返回对应的LocalDate实例。这个方法的好处是没再犯老API的设计错误,比如年度起始于1900,月份是从0开 始等等。
package com.shxt.demo02; import java.time.LocalDate; public class Demo03 { public static void main(String[] args) { LocalDate date = LocalDate.of(2018,2,6); System.out.println("自定义日期:"+date); } }
package com.shxt.demo02; import java.time.LocalDate; public class Demo04 { public static void main(String[] args) { LocalDate date1 = LocalDate.now(); LocalDate date2 = LocalDate.of(2018,2,5); if(date1.equals(date2)){ System.out.println("时间相等"); }else{ System.out.println("时间不等"); } } }
package com.shxt.demo02; import java.time.LocalDate; import java.time.MonthDay; public class Demo05 { public static void main(String[] args) { LocalDate date1 = LocalDate.now(); LocalDate date2 = LocalDate.of(2018,2,6); MonthDay birthday = MonthDay.of(date2.getMonth(),date2.getDayOfMonth()); MonthDay currentMonthDay = MonthDay.from(date1); if(currentMonthDay.equals(birthday)){ System.out.println("是你的生日"); }else{ System.out.println("你的生日还没有到"); } } }
只要当天的日期和生日匹配,无论是哪一年都会打印出祝贺信息。你可以把程序整合进系统时钟,看看生日时是否会受到提醒,或者写一个单元测试来检测代码是否运行正确。
package com.shxt.demo02; import java.time.LocalTime; public class Demo06 { public static void main(String[] args) { LocalTime time = LocalTime.now(); System.out.println("获取当前的时间,不含有日期:"+time); } }
可以看到当前时间就只包含时间信息,没有日期
通过增加小时、分、秒来计算将来的时间很常见。Java 8除了不变类型和线程安全的好处之外,还提供了更好的plusHours()方法替换add(),并且是兼容的。注意,这些方法返回一个全新的LocalTime实例,由于其不可变性,返回后一定要用变量赋值。
package com.shxt.demo02; import java.time.LocalTime; public class Demo07 { public static void main(String[] args) { LocalTime time = LocalTime.now(); LocalTime newTime = time.plusHours(3); System.out.println("三个小时后的时间为:"+newTime); } }
和上个例子计算3小时以后的时间类似,这个例子会计算一周后的日期。LocalDate日期不包含时间信息,它的plus()方法用来增加天、周、月,ChronoUnit类声明了这些时间单位。由于LocalDate也是不变类型,返回后一定要用变量赋值。
package com.shxt.demo02; import java.time.LocalDate; import java.time.temporal.ChronoUnit; public class Demo08 { public static void main(String[] args) { LocalDate today = LocalDate.now(); System.out.println("今天的日期为:"+today); LocalDate nextWeek = today.plus(1, ChronoUnit.WEEKS); System.out.println("一周后的日期为:"+nextWeek); } }
可以看到新日期离当天日期是7天,也就是一周。你可以用同样的方法增加1个月、1年、1小时、1分钟甚至一个世纪,更多选项可以查看Java 8 API中的ChronoUnit类
利用minus()方法计算一年前的日期
package com.shxt.demo02; import java.time.LocalDate; import java.time.temporal.ChronoUnit; public class Demo09 { public static void main(String[] args) { LocalDate today = LocalDate.now(); LocalDate previousYear = today.minus(1, ChronoUnit.YEARS); System.out.println("一年前的日期 : " + previousYear); LocalDate nextYear = today.plus(1, ChronoUnit.YEARS); System.out.println("一年后的日期:"+nextYear); } }
Java 8增加了一个Clock时钟类用于获取当时的时间戳,或当前时区下的日期时间信息。以前用到System.currentTimeInMillis()和TimeZone.getDefault()的地方都可用Clock替换。
package com.shxt.demo02; import java.time.Clock; public class Demo10 { public static void main(String[] args) { // Returns the current time based on your system clock and set to UTC. Clock clock = Clock.systemUTC(); System.out.println("Clock : " + clock.millis()); // Returns time based on system clock zone Clock defaultClock = Clock.systemDefaultZone(); System.out.println("Clock : " + defaultClock.millis()); } }
另一个工作中常见的操作就是如何判断给定的一个日期是大于某天还是小于某天?在Java 8中,LocalDate类有两类方法isBefore()和isAfter()用于比较日期。调用isBefore()方法时,如果给定日期小于当前日期则返回true。
package com.shxt.demo02; import java.time.LocalDate; import java.time.temporal.ChronoUnit; public class Demo11 { public static void main(String[] args) { LocalDate today = LocalDate.now(); LocalDate tomorrow = LocalDate.of(2018,2,6); if(tomorrow.isAfter(today)){ System.out.println("之后的日期:"+tomorrow); } LocalDate yesterday = today.minus(1, ChronoUnit.DAYS); if(yesterday.isBefore(today)){ System.out.println("之前的日期:"+yesterday); } } }
Java 8不仅分离了日期和时间,也把时区分离出来了。现在有一系列单独的类如ZoneId来处理特定时区,ZoneDateTime类来表示某时区下的时间。这在Java 8以前都是 GregorianCalendar类来做的。下面这个例子展示了如何把本时区的时间转换成另一个时区的时间。
package com.shxt.demo02; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZonedDateTime; public class Demo12 { public static void main(String[] args) { // Date and time with timezone in Java 8 ZoneId america = ZoneId.of("America/New_York"); LocalDateTime localtDateAndTime = LocalDateTime.now(); ZonedDateTime dateAndTimeInNewYork = ZonedDateTime.of(localtDateAndTime, america ); System.out.println("Current date and time in a particular timezone : " + dateAndTimeInNewYork); } }
与 MonthDay检查重复事件的例子相似,YearMonth是另一个组合类,用于表示信用卡到期日、FD到期日、期货期权到期日等。还可以用这个类得到 当月共有多少天,YearMonth实例的lengthOfMonth()方法可以返回当月的天数,在判断2月有28天还是29天时非常有用。
package com.shxt.demo02; import java.time.*; public class Demo13 { public static void main(String[] args) { YearMonth currentYearMonth = YearMonth.now(); System.out.printf("Days in month year %s: %d%n", currentYearMonth, currentYearMonth.lengthOfMonth()); YearMonth creditCardExpiry = YearMonth.of(2019, Month.FEBRUARY); System.out.printf("Your credit card expires on %s %n", creditCardExpiry); } }
package com.shxt.demo02; import java.time.LocalDate; public class Demo14 { public static void main(String[] args) { LocalDate today = LocalDate.now(); if(today.isLeapYear()){ System.out.println("This year is Leap year"); }else { System.out.println("2018 is not a Leap year"); } } }
有一个常见日期操作是计算两个日期之间的天数、周数或月数。在Java 8中可以用java.time.Period类来做计算。
下面这个例子中,我们计算了当天和将来某一天之间的月数。
package com.shxt.demo02; import java.time.LocalDate; import java.time.Period; public class Demo15 { public static void main(String[] args) { LocalDate today = LocalDate.now(); LocalDate java8Release = LocalDate.of(2018, 12, 14); Period periodToNextJavaRelease = Period.between(today, java8Release); System.out.println("Months left between today and Java 8 release : " + periodToNextJavaRelease.getMonths() ); } }
Instant类有一个静态工厂方法now()会返回当前的时间戳,如下所示:
package com.shxt.demo02; import java.time.Instant; public class Demo16 { public static void main(String[] args) { Instant timestamp = Instant.now(); System.out.println("What is value of this instant " + timestamp.toEpochMilli()); } }
时间戳信息里同时包含了日期和时间,这和java.util.Date很像。实际上Instant类确实等同于 Java 8之前的Date类,你可以使用Date类和Instant类各自的转换方法互相转换,例如:Date.from(Instant) 将Instant转换成java.util.Date,Date.toInstant()则是将Date类转换成Instant类。
package com.shxt.demo02; import java.time.LocalDate; import java.time.format.DateTimeFormatter; public class Demo17 { public static void main(String[] args) { String dayAfterTommorrow = "20180205"; LocalDate formatted = LocalDate.parse(dayAfterTommorrow, DateTimeFormatter.BASIC_ISO_DATE); System.out.println(dayAfterTommorrow+" 格式化后的日期为: "+formatted); } }
package com.shxt.demo02; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; public class Demo18 { public static void main(String[] args) { LocalDateTime date = LocalDateTime.now(); DateTimeFormatter format1 = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"); //日期转字符串 String str = date.format(format1); System.out.println("日期转换为字符串:"+str); DateTimeFormatter format2 = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"); //字符串转日期 LocalDate date2 = LocalDate.parse(str,format2); System.out.println("日期类型:"+date2); } }
vue 中有一个 keep-alive 的功能,可以将组件渲染结果缓存起来,结合 vue-router 中使用,可以缓存某个 view 的整个内容(比如列表页)。
通常会有这样的需求:
用户单击列表页(List)中某个标题,进入到详情页(Detail)。
此后会有两种情况返回到 List:
用户点击导航栏中的 /list 链接;
用户点击浏览器返回按钮 ←
情况 1 发生时,我们希望用户进入 List 后,页面的数据需要刷新。用户在导航切换时(点击导航菜单中的链接),其意愿是需要页面刷新,由于我们的应用是 SPA 应用,所以需要主动重新发送 ajax 请求获取数据,以让用户知晓,页面的数据有变化,否则他还要手动去点击浏览器的刷新按钮。
情况 2 发生时,我们希望用户进入 List 后,按照 h5 的行为自动将滚动条还原到原来位置,并且此时不应刷新页面数据,直接使用缓存。
需要缓存数据,那么在 app.vue 中的 router-view 配合使用 keep-alive :
入口文件(app.vue):
//- app.vue <template> #app .page-header .page-main keep-alive router-view(v-if="$route.meta.keep_alive") router-view(v-if="!$route.meta.keep_alive") .page-footer </template>
注: v-if 是加在 router-view 上,而不是加在 keep-alive 上。
路由配置(router.coffee):
# router.coffee router = new VueRouter( mode: 'history' # 需要使用 h5 模式,才能使用 scrollBehavior routes: [ { path: '/list' name: 'list' meta: title: '列表页' keep_alive: true # true 表示需要使用缓存 component: require('../views/list.vue') }, ... ] scrollBehavior: (to, from, saved_position) -> # 保存到 meta 中,备用 to.meta.saved_position = saved_position return saved_position ? {x: 0, y: 0} )
注:
某一个 route 的 meta.keep_alive 值,设定之后就不能再修改其值,即不能动态变更 meta.keep_alive 的方式来动态控制是否使用缓存。
原因:某个 route.meta.keep_alive 初始设定为 true,会有以下两种情况:
若在 scrollBehavior 中判断出是情况 1,此时如果使用 to.meta.keep_alive = false
来禁用缓存,那么 app.vue 中的两个 router-view 会切换,之前缓存在 <keep-alive/> 中的第一个 router-view 的数据,会被 v-if 清空,导致缓存失效;
同理,当判断出是情况 2,如果 to.meta.keep_alive = true
来开启缓存,此时又会发生一次切换,导致重新生成组件。故 route.meta.keep_alive 一旦设定,就表示此组件一定是需要缓存的,后期不能改变其值。
既然不能改变 meta.keep_alive 的值,那么如果做到在 keep-alive 包裹内的组件进行数据刷新呢?
答案是:使用 vue-router 中的 beforeRouteEnter 钩子。
这里不能使用 vue 文档中的 mounted、updated 等生命周期中的钩子函数,因为在使用 keep-alive 后,这些钩子,统统不会被调用。
而在 vue-router 中提供了 beforeRouteEnter 钩子。此钩子与 keep-alive 无关,不会像 mounted 等内置钩子那样被 keep-alive “吃掉”。
# list.coffee export default List = name: 'List' data: -> lists: [] beforeRouteEnter: (to, from, next) -> next (vm) -> # 如果 saved_position === null,那么说明是点击了导航链接(情况1), # 此时需要刷新数据,获取新的列表内容。 # 否则什么也不做,直接使用 keep-alive 中的缓存。 if to.meta.saved_position is null vm.fetchLists() methods: fetchLists: -> @$store .dispatch(types.dashboard.actions.GET_LISTS) .then (res) => if res.status == 'success' @posts = res.data else @posts_loading_error_message = res.data.msg
注: 注意 beforeRouteEnter 中使用的是 is null,而不是 if to.meta.saved_position?。
一点说明:
is null 相当于 === null;
to.meta.saved_position? 相当于 to.meta.saved_position == null。
undefined == null 为 true,但 undefined === null 为 false。
这一点很重要,因为在调试过程中,如果你在 beforeRouteEnter 的 next 方法中:
console.log to.meta.saved_position
当点击的是导航链接(情况 1),此时输出 null,因为 scrollBehavior 中的 saved_position 为 null。
这里显示相关代码:
// src/util/scroll.js#L40// wait until re-render finishes before scrollingrouter.app.$nextTick(() => { let position = getScrollPosition() const shouldScroll = behavior(to, from, isPop ? position : null)
可以看出 isPop ? position : null 当点击导航链接(情况 1, isPop == false)时,传递的是 null。
其中 isPop 可以理解为是否是点击了浏览器后退按钮。点击了就传递 position,否则传递 null。
那么什么时候会出现 undefined 的情况呢?
上面代码中的 position = getScrollPosition(),其 源码 如下:
function getScrollPosition (): ?Object { const key = getStateKey() if (key) { return positionStore[key] }}
其中 positionStore 的值由 函数 saveScrollPosition 保存。
如果找到了 key 就返回,此时为形如 {x: 33, y: 12} 的值,表示之前横纵滚动条的位置。
如果没有找到 key,此函数没有返回任何内容,那么自然是返回 undefined 了。
1.说到数据库事务,人们脑海里自然不自然的就会浮现出事务的四大特性、四大隔离级别、七大传播特性。四大还好说,问题是七大传播特性是哪儿来的?是Spring在当前线程内,处理多个数据库操作方法事务时所做的一种事务应用策略。事务本身并不存在什么传播特性,不要混淆事务本身和Spring的事务应用策略。(当然,找工作面试时,还是可以巧妙的描述传播特性的)
2.一说到事务,人们可能又会想起create、begin、commit、rollback、close、suspend。可实际上,只有commit、rollback是实际存在的,剩下的create、begin、close、suspend都是虚幻的,是业务层或数据库底层应用语意,而非JDBC事务的真实命令。
create(事务创建):不存在。
begin(事务开始):姑且认为存在于DB的命令行中,比如Mysql的start transaction命令,以及其他数据库中的begin transaction命令。JDBC中不存在。
close(事务关闭):不存在。应用程序接口中的close()方法,是为了把connection放回数据库连接池中,供下一次使用,与事务毫无关系。
suspend(事务挂起):不存在。Spring中事务挂起的含义是,需要新事务时,将现有的connection1保存起来(它还有尚未提交的事务),然后创建connection2,connection2提交、回滚、关闭完毕后,再把connection1取出来,完成提交、回滚、关闭等动作,保存connection1的动作称之为事务挂起。在JDBC中,是根本不存在事务挂起的说法的,也不存在这样的接口方法。
因此,记住事务的三个真实存在的方法,不要被各种事务状态名词所迷惑,它们分别是:conn.setAutoCommit()、conn.commit()、conn.rollback()。
conn.close()含义为关闭一个数据库连接,这已经不再是事务方法了。
public interface Transaction { Connection getConnection() throws SQLException; void commit() throws SQLException; void rollback() throws SQLException; void close() throws SQLException; }
有了文章开头的分析,当你再次看到close()方法时,千万别再认为是关闭一个事务了,而是关闭一个conn连接,或者是把conn连接放回连接池内。
事务类层次结构图:
JdbcTransaction:单独使用Mybatis时,默认的事务管理实现类,就和它的名字一样,它就是我们常说的JDBC事务的极简封装,和编程使用mysql-connector-java-5.1.38-bin.jar事务驱动没啥差别。其极简封装,仅是让connection支持连接池而已。
ManagedTransaction:含义为托管事务,空壳事务管理器,皮包公司。仅是提醒用户,在其它环境中应用时,把事务托管给其它框架,比如托管给Spring,让Spring去管理事务。
org.apache.ibatis.transaction.jdbc.JdbcTransaction.java部分源码。
@Override public void close() throws SQLException { if (connection != null) { resetAutoCommit(); if (log.isDebugEnabled()) { log.debug("Closing JDBC Connection [" + connection + "]"); } connection.close(); } }
面对上面这段代码,我们不禁好奇,connection.close()之前,居然调用了一个resetAutoCommit(),含义为重置autoCommit属性值。connection.close()含义为销毁conn,既然要销毁conn,为何还多此一举的调用一个resetAutoCommit()呢?消失之前多喝口水,真的没有必要。
其实,原因是这样的,connection.close()不意味着真的要销毁conn,而是要把conn放回连接池,供下一次使用,既然还要使用,自然就需要重置AutoCommit属性了。通过生成connection代理类,来实现重回连接池的功能。如果connection是普通的Connection实例,那么代码也是没有问题的,双重支持。
顾名思义,一个生产JdbcTransaction实例,一个生产ManagedTransaction实例。两个毫无实际意义的工厂类,除了new之外,没有其他代码。
<transactionManager type="JDBC" />
mybatis-config.xml配置文件内,可配置事务管理类型。
无论是SqlSession,还是Executor,它们的事务方法,最终都指向了Transaction的事务方法,即都是由Transaction来完成事务提交、回滚的。
配一个简单的时序图。
代码样例:
public static void main(String[] args) { SqlSession sqlSession = MybatisSqlSessionFactory.openSession(); try { StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class); Student student = new Student(); student.setName("yy"); student.setEmail("email@email.com"); student.setDob(new Date()); student.setPhone(new PhoneNumber("123-2568-8947")); studentMapper.insertStudent(student); sqlSession.commit(); } catch (Exception e) { sqlSession.rollback(); } finally { sqlSession.close(); } }
注:Executor在执行insertStudent(student)方法时,与事务的提交、回滚、关闭毫无瓜葛(方法内部不会提交、回滚事务),需要像上面的代码一样,手动显示调用commit()、rollback()、close()等方法。
因此,后续在分析到类似insert()、update()等方法内部时,需要忘记事务的存在,不要试图在insert()等方法内部寻找有关事务的任何方法。
1. 一个conn生命周期内,可以存在无数多个事务。
// 执行了connection.setAutoCommit(false),并返回 SqlSession sqlSession = MybatisSqlSessionFactory.openSession(); try { StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class); Student student = new Student(); student.setName("yy"); student.setEmail("email@email.com"); student.setDob(new Date()); student.setPhone(new PhoneNumber("123-2568-8947")); studentMapper.insertStudent(student); // 提交 sqlSession.commit(); studentMapper.insertStudent(student); // 多次提交 sqlSession.commit(); } catch (Exception e) { // 回滚,只能回滚当前未提交的事务 sqlSession.rollback(); } finally { sqlSession.close(); }
对于JDBC来说,autoCommit=false时,是自动开启事务的,执行commit()后,该事务结束。以上代码正常情况下,开启了2个事务,向数据库插入了2条数据。JDBC中不存在Hibernate中的session的概念,在JDBC中,insert了几次,数据库就会有几条记录,切勿混淆。而rollback(),只能回滚当前未提交的事务。
2. autoCommit=false,没有执行commit(),仅执行close(),会发生什么?
try { studentMapper.insertStudent(student); } finally { sqlSession.close(); }
就像上面这样的代码,没有commit(),固执的程序员总是好奇这样的特例。
insert后,close之前,如果数据库的事务隔离级别是read uncommitted,那么,我们可以在数据库中查询到该条记录。
接着执行sqlSession.close()时,经过SqlSession的判断,决定执行rollback()操作,于是,事务回滚,数据库记录消失。
下面,我们看看org.apache.ibatis.session.defaults.DefaultSqlSession.java中的close()方法源码。
@Override public void close() { try { executor.close(isCommitOrRollbackRequired(false)); dirty = false; } finally { ErrorContext.instance().reset(); } }
事务是否回滚,依靠isCommitOrRollbackRequired(false)方法来判断。
private boolean isCommitOrRollbackRequired(boolean force) { return (!autoCommit && dirty) || force; }
在上面的条件判断中,!autoCommit=true(取反当然是true了),force=false,最终是否回滚事务,只有dirty参数了,dirty含义为是否是脏数据。
@Override public int insert(String statement, Object parameter) { return update(statement, parameter); } @Override public int update(String statement, Object parameter) { try { dirty = true; MappedStatement ms = configuration.getMappedStatement(statement); return executor.update(ms, wrapCollection(parameter)); } catch (Exception e) { throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
源码很明确,只要执行update操作,就设置dirty=true。insert、delete最终也是执行update操作。
只有在执行完commit()、rollback()、close()等方法后,才会再次设置dirty=false。
@Override public void commit(boolean force) { try { executor.commit(isCommitOrRollbackRequired(force)); dirty = false; } catch (Exception e) { throw ExceptionFactory.wrapException("Error committing transaction. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
因此,得出结论:autoCommit=false,但是没有手动commit,在sqlSession.close()时,Mybatis会将事务进行rollback()操作,然后才执行conn.close()关闭连接,当然数据最终也就没能持久化到数据库中了。
3. autoCommit=false,没有commit,也没有close,会发生什么?
studentMapper.insertStudent(student);
干脆,就这一句话,即不commit,也不close。
结论:insert后,jvm结束前,如果事务隔离级别是read uncommitted,我们可以查到该条记录。jvm结束后,事务被rollback(),记录消失。通过断点debug方式,你可以看到效果。
这说明JDBC驱动实现,已经Kao虑到这样的特例情况,底层已经有相应的处理机制了。这也超出了我们的探究范围。
但是,一万个屌丝程序员会对你说:Don't do it like this. Go right way。
警告:请按正确的try-catch-finally编程方式处理事务,若不从,本人概不负责后果。
注:无参的openSession()方法,会自动设置autoCommit=false。
总结:Mybatis的JdbcTransaction,和纯粹的Jdbc事务,几乎没有差别,它仅是扩展支持了连接池的connection。
另外,需要明确,无论你是否手动处理了事务,只要是对数据库进行任何update操作(update、delete、insert),都一定是在事务中进行的,这是数据库的设计规范之一。读完本篇文章,是否颠覆了你心中目前对事务的理解呢?
今天我们来分享一款模拟汉字书写笔画特效果,我们先看图:
这款效果的代码比较少,我们直接上代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>HTML5 汉字书写笔画特效 </title>
<script src="js/hanzi-writer.min.js"></script>
<script src="js/polyfill.min.js"></script>
</head>
<body>
<div id="character-target-div"></div>
<button id="animate-button">重写</button>
<script>
var char = '你好世界',
writer = [];
for(var x = 0; x < char.length; x++) {
writer.push(writeChar(char[x]))
}
function writeChar(char) {
return HanziWriter.create('character-target-div', char, {
width: 100,
height: 100,
padding: 5,
showOutline: true
});
}
document.getElementById('animate-button').addEventListener('click', function() {
if(writer.length > 0) {
writer.map(function(w) {
w.animateCharacter()
})
}
});
</script>
</body>
</html>
这个用到js文件一起在附件里,那么我们下期再见了
今天我们来分享一款很好看的千纸鹤飞翔效果,如图所示:
主要是使用到Canvas这个额技术。先看html5的代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>HTML5 Canvas千纸鹤动画特效</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div id="controls"></div>
<script src='js/p5.min.js'></script>
<script src="js/index.js"></script>
</body>
</html>
然后是样式的效果:
* {
margin: 0;
padding: 0;
}
html, body {
width: 100%;
height: 100%;
overflow: hidden;
background: black;
}
canvas {
display: block;
}
#controls {
z-index: 2;
margin: 20px;
position: absolute;
top: 0;
left: 0;
color: white;
}
接下来是脚本:
birds = [];
let numBirds = 50;
class Bird{
constructor(){
this.pos = [random(width), random(height)];
this.size = pow(random(), 2)*80 + 40;
this.angle = random(PI/16) + PI/8;
this.speed = this.size/600;
this.hue = random();
let p = [
[0, 0],
[random(.00, .10), random(.1, .4)], //beak
[random(.10, .50), -random(.1, .2)], //head
[random(.50, .80), random(.1, .3)], //body
[random(.80, 1.0), -random(.2, .4)], //tail
[1, 0],
[random(.30, .40), 0], //wing base 1
[random(.80, .90), 0], //wing base 2
[random(.50, .65), -random(.4, .6)], //wing tip 1
[random(.65, .70), -random(.4, .6)], //wing tip 2
];
this.triangles = [
// idx, idx, idx, color , isWing
[...p[0], ...p[1], ...p[2], randColor(this.hue), false], //head
[...p[3], ...p[4], ...p[5], randColor(this.hue), false], //tail
[...p[6], ...p[7], ...p[8], randColor(this.hue), true ], //back wing
[...p[0], ...p[2], ...p[3], randColor(this.hue), false], //body 1
[...p[2], ...p[3], ...p[5], randColor(this.hue), false], //body 2
[...p[6], ...p[7], ...p[9], randColor(this.hue), true ], //front wing
]
}
render(){
push();
let p = this.pos;
translate(...p);
scale(this.size);
strokeWeight(1/this.size);
p[0] += cos(this.angle+PI)*this.speed*20;
p[1] += sin(this.angle+PI)*this.speed*20;
if (p[0] < -this.size ) p[0] += width +this.size;
if (p[1] < -this.size/2) p[1] += height+this.size/2;
let s = cos(frameCount*this.speed)
rotate(this.angle);
this.triangles.map(t => {
fill(...(t[6]))
if (t[7]) triangle(...t.slice(0, 5), t[5]*s);
else triangle(...t);
})
pop();
}
}
function randColor(base = 0, amt=.2){
return [(base+random(amt)-amt/2)%1, .2 + random(amt), .8 + random(amt)];
}
function setup (){
pixelDensity(1);
createCanvas();
colorMode(HSB, 1, 1, 1);
windowResized();
}
function init(){
birds = [];
for (let i = 0; i < numBirds; i++) birds.push(new Bird());
birds = birds.sort((a,b) => a.size - b.size);
}
function draw(){
background(0, .5);
birds.map(b => b.render());
}
function mousePressed(){windowResized();}
function windowResized(){
resizeCanvas(windowWidth, windowHeight);
init();
}
这期就到这里,下期见。
在程序设计中,有很多的“公约”,遵守约定去实现你的代码,会让你避开很多坑,这些公约是前人总结出来的设计规范。
Object类是Java中的万类之祖,其中,equals和hashCode是2个非常重要的方法。
这2个方法总是被人放在一起讨论。最近在看集合框架,为了打基础,就决定把一些细枝末节清理掉。一次性搞清楚!
下面开始剖析。
public boolean equals(Object obj)
Object类中默认的实现方式是 : return this == obj 。那就是说,只有this 和 obj引用同一个对象,才会返回true。
而我们往往需要用equals来判断 2个对象是否等价,而非验证他们的唯一性。这样我们在实现自己的类时,就要重写equals.
按照约定,equals要满足以下规则。
自反性: x.equals(x) 一定是true
对null: x.equals(null) 一定是false
对称性: x.equals(y) 和 y.equals(x)结果一致
传递性: a 和 b equals , b 和 c equals,那么 a 和 c也一定equals。
一致性: 在某个运行时期间,2个对象的状态的改变不会不影响equals的决策结果,那么,在这个运行时期间,无论调用多少次equals,都返回相同的结果。
一个例子
equals编写指导
Test类对象有2个字段,num和data,这2个字段代表了对象的状态,他们也用在equals方法中作为评判的依据。
在第8行,传入的比较对象的引用和this做比较,这样做是为了 save time ,节约执行时间,如果this 和 obj是 对同一个堆对象的引用,那么,他们一定是qeuals 的。
接着,判断obj是不是为null,如果为null,一定不equals,因为既然当前对象this能调用equals方法,那么它一定不是null,非null 和 null当然不等价。
然后,比较2个对象的运行时类,是否为同一个类。不是同一个类,则不equals。getClass返回的是 this 和obj的运行时类的引用。如果他们属于同一个类,则返回的是同一个运行时类的引用。注意,一个类也是一个对象。
1、 有些程序员使用下面的第二种写法替代第一种比较运行时类的写法。应该避免这样做。
它违反了公约中的对称原则。
例如:假设Dog扩展了Aminal类。
这就会导致
仅当Test类没有子类的时候,这样做才能保证是正确的。
3、按照第一种方法实现,那么equals只能比较同一个类的对象,不同类对象永远是false。但这并不是强制要求的。一般我们也很少需要在不同的类之间使用equals。
4、在具体比较对象的字段的时候,对于基本值类型的字段,直接用 == 来比较(注意浮点数的比较,这是一个坑)对于引用类型的字段,你可以调用他们的equals,当然,你也需要处理字段为null 的情况。对于浮点数的比较,我在看Arrays.binarySearch的源代码时,发现了如下对于浮点数的比较的技巧:
5、并不总是要将对象的所有字段来作为equals 的评判依据,那取决于你的业务要求。比如你要做一个家电功率统计系统,如果2个家电的功率一样,那就有足够的依据认为这2个家电对象等价了,至少在你这个业务逻辑背景下是等价的,并不关心他们的价钱啊,品牌啊,大小等其他参数。
6、最后需要注意的是,equals 方法的参数类型是Object,不要写错!
public int hashCode()
这个方法返回对象的散列码,返回值是int类型的散列码。
对象的散列码是为了更好的支持基于哈希机制的Java集合类,例如 Hashtable, HashMap, HashSet 等。
关于hashCode方法,一致的约定是:
重写了euqls方法的对象必须同时重写hashCode()方法。
如果2个对象通过equals调用后返回是true,那么这个2个对象的hashCode方法也必须返回同样的int型散列码
如果2个对象通过equals返回false,他们的hashCode返回的值允许相同。(然而,程序员必须意识到,hashCode返回独一无二的散列码,会让存储这个对象的hashtables更好地工作。)
在上面的例子中,Test类对象有2个字段,num和data,这2个字段代表了对象的状态,他们也用在equals方法中作为评判的依据。那么, 在hashCode方法中,这2个字段也要参与hash值的运算,作为hash运算的中间参数。这点很关键,这是为了遵守:2个对象equals,那么 hashCode一定相同规则。
也是说,参与equals函数的字段,也必须都参与hashCode 的计算。
合乎情理的是:同一个类中的不同对象返回不同的散列码。典型的方式就是根据对象的地址来转换为此对象的散列码,但是这种方式对于Java来说并不是唯一的要求的
的实现方式。通常也不是最好的实现方式。
相比 于 equals公认实现约定,hashCode的公约要求是很容易理解的。有2个重点是hashCode方法必须遵守的。约定的第3点,其实就是第2点的细化,下面我们就来看看对hashCode方法的一致约定要求。
在某个运行时期间,只要对象的(字段的)变化不会影响equals方法的决策结果,那么,在这个期间,无论调用多少次hashCode,都必须返回同一个散列码。
通过equals调用返回true 的2个对象的hashCode一定一样。
通过equasl返回false 的2个对象的散列码不需要不同,也就是他们的hashCode方法的返回值允许出现相同的情况。
总结一句话:等价的(调用equals返回true)对象必须产生相同的散列码。不等价的对象,不要求产生的散列码不相同。
hashCode编写指导
在编写hashCode时,你需要考虑的是,最终的hash是个int值,而不能溢出。不同的对象的hash码应该尽量不同,避免hash冲突。
那么如果做到呢?下面是解决方案。
定义一个int类型的变量 hash,初始化为 7。
接下来让你认为重要的字段(equals中衡量相等的字段)参入散列运,算每一个重要字段都会产生一个hash分量,为最终的hash值做出贡献(影响)
运算方法参考表
最后把所有的分量都总和起来,注意并不是简单的相加。选择一个倍乘的数字31,参与计算。然后不断地递归计算,直到所有的字段都参与了。
mybatis-plus是完全基于mybatis开发的一个增强工具,它的设计理念是在mybatis的基础上只做增强不做改变,为简化开发、提高效率而生,它在mybatis的基础上增加了很多实用性的功能,比如增加了乐观锁插件、字段自动填充功能、分页插件、条件构造器、sql注入器等等,这些在开发过程中都是非常实用的功能,mybatis-plus可谓是站在巨人的肩膀上进行了一系列的创新,我个人极力推荐。下面我会详细地从源码的角度分析mybatis-plus(下文简写成mp)是如何实现sql自动注入的原理。
我们回顾一下mybatis的Mapper的注册与绑定过程,我之前也写过一篇「Mybatis源码分析之Mapper注册与绑定」,在这篇文章中,我详细地讲解了Mapper绑定的最终目的是将xml或者注解上的sql信息与其对应Mapper类注册到MappedStatement中,既然mybatis-plus的设计理念是在mybatis的基础上只做增强不做改变,那么sql注入器必然也是在将我们预先定义好的sql和预先定义好的Mapper注册到MappedStatement中。
现在我将Mapper的注册与绑定过程用时序图再梳理一遍:
解析一下这几个类的作用:
SqlSessionFactoryBean:继承了FactoryBean和InitializingBean,符合spring loc容器bean的基本规范,可在获取该bean时调用getObject()方法到SqlSessionFactory。
XMLMapperBuilder:xml文件解析器,解析Mapper对应的xml文件信息,并将xml文件信息注册到Configuration中。
XMLStatementBuilder:xml节点解析器,用于构建select/insert/update/delete节点信息。
MapperBuilderAssistant:Mapper构建助手,将Mapper节点信息封装成statement添加到MappedStatement中。
MapperRegistry:Mapper注册与绑定类,将Mapper的类信息与MapperProxyFactory绑定。
MapperAnnotationBuilder:Mapper注解解析构建器,这也是为什么mybatis可以直接在Mapper方法添加注解信息就可以不用在xml写sql信息的原因,这个构建器专门用于解析Mapper方法注解信息,并将这些信息封装成statement添加到MappedStatement中。
从时序图可知,Configuration配置类存储了所有Mapper注册与绑定的信息,然后创建SqlSessionFactory时再将Configuration注入进去,最后经过SqlSessionFactory创建出来的SqlSession会话,就可以根据Configuration信息进行数据库交互,而MapperProxyFactory会为每个Mapper创建一个MapperProxy代理类,MapperProxy包含了Mapper操作SqlSession所有的细节,因此我们就可以直接使用Mapper的方法就可以跟SqlSession进行交互。
源码分析
从Mapper的注册与绑定过程的时序图看,要想将sql注入器无缝链接地添加到mybatis里面,那就得从Mapper注册步骤添加,果然,mp很鸡贼地继承了MapperRegistry这个类然后重写了addMapper方法:
方法中将MapperAnnotationBuilder替换成了自家的MybatisMapperAnnotationBuilder,在这里特别说明一下,mp为了不更改mybatis原有的逻辑,会用继承或者直接粗暴地将其复制过来,然后在原有的类名上加上前缀“Mybatis”。
sql注入器就是从这个方法里面添加上去的,首先判断Mapper是否是BaseMapper的超类或者超接口,BaseMapper是mp的基础Mapper,里面定义了很多默认的基础方法,意味着我们一旦使用上mp,通过sql注入器,很多基础的数据库操作都可以直接继承BaseMapper实现了,开发效率爆棚有木有!
GlobalConfiguration是mp的全局缓存类,用于存放mp自带的一些功能,很明显,sql注入器就存放在GlobalConfiguration中。
这个方法是先从全局缓存类中获取自定义的sql注入器,如果在GlobalConfiguration中没有找到自定义sql注入器,就会设置一个mp默认的sql注入器AutoSqlInjector。
sql注入器接口:
所有自定义的sql注入器都需要实现ISqlInjector接口,mp已经为我们默认实现了一些基础的注入器:
其中AutoSqlInjector提供了最基本的sql注入,以及一些通用的sql注入与拼装的逻辑,LogicSqlInjector在AutoSqlInjector的基础上复写了删除逻辑,因为我们的数据库的数据删除实质上是软删除,并不是真正的删除。
该方法是sql注入器的入口,在入口处添加了注入过后不再注入的判断功能。
注入之前先将Mapper类提取泛型模型,因为继承BaseMapper需要将Mapper对应的model添加到泛型里面,这时候我们需要将其提取出来,提取出来后还需要将其初始化成一个TableInfo对象,TableInfo存储了数据库对应的model所有的信息,包括表主键ID类型、表名称、表字段信息列表等等信息,这些信息通过反射获取。
所有需要注入的sql都是通过该方法进行调用,AutoSqlInjector还提供了一个inject方法,自定义sql注入器时,继承AutoSqlInjector,实现该方法就行了。
我随机选择一个删除sql的注入,其它sql注入都是类似这么写,SqlMethod是一个枚举类,里面存储了所有自动注入的sql与方法名,如果是批量操作,SqlMethod的定义的sql语句在添加批量操作的语句。再根据table和sql信息创建一个SqlSource对象。
sql注入器的最终操作,这里会判断MappedStatement是否存在,这个判断是有原因的,它会防止重复注入,如果你的Mapper方法已经在Mybatis的逻辑里面注册了,mp不会再次注入。最后调用MapperBuilderAssistant助手类的addMappedStatement方法执行注册操作。
今天来分享一款js拖拽到边缘悬浮球代码,仿360加速球的,下面来放图:
下面我们来看代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>js拖拽到边缘悬浮球代码 </title>
<style>
* {
margin: 0;
padding: 0;
list-style: none;
}
html, body {
width: 100%;
height: 100%;
}
#neko {
width: 100px;
height: 100px;
background: #ddd;
position: fixed;
cursor: move;
box-sizing: border-box;
border: 4px solid #66cc66;
border-radius: 50%;
background: url('tp.png') no-repeat center center;
background-size: 100% 100%;
overflow: hidden;
}
</style>
</head>
<body>
<h3 style="padding: 20px">请将图片拖动到浏览器边缘,查看效果</h3>
<div id="neko"></div>
<script>
var neko = document.querySelector('#neko');
var nekoW = neko.offsetWidth;
var nekoH = neko.offsetHeight;
var cuntW = 0;
var cuntH = 0;
neko.style.left = parseInt(Math.random() * (document.body.offsetWidth - nekoW)) + 'px';
neko.style.top = parseInt(Math.random() * (document.body.offsetHeight - nekoH)) + 'px';
function move(obj, w, h) {
if(obj.direction === 'left') {
obj.style.left = 0 - w + 'px';
} else if(obj.direction === 'right') {
obj.style.left = document.body.offsetWidth - nekoW + w + 'px';
}
if(obj.direction === 'top') {
obj.style.top = 0 - h + 'px';
} else if(obj.direction === 'bottom') {
obj.style.top = document.body.offsetHeight - nekoH + h + 'px';
}
}
function rate(obj, a) {
// console.log(a);
obj.style.transform = ' rotate(' + a + ')'
}
function action(obj) {
var dir = obj.direction;
switch(dir) {
case 'left':
rate(obj, '90deg');
break;
case 'right':
rate(obj, '-90deg');
break;
case 'top':
rate(obj, '-180deg');
break;
default:
rate(obj, '-0');
break;
}
}
neko.onmousedown = function(e) {
var nekoL = e.clientX - neko.offsetLeft;
var nekoT = e.clientY - neko.offsetTop;
document.onmousemove = function(e) {
cuntW = 0;
cuntH = 0;
neko.direction = '';
neko.style.transition = '';
neko.style.left = (e.clientX - nekoL) + 'px';
neko.style.top = (e.clientY - nekoT) + 'px';
if(e.clientX - nekoL < 5) {
neko.direction = 'left';
}
if(e.clientY - nekoT < 5) {
neko.direction = 'top';
}
if(e.clientX - nekoL > document.body.offsetWidth - nekoW - 5) {
neko.direction = 'right';
}
if(e.clientY - nekoT > document.body.offsetHeight - nekoH - 5) {
neko.direction = 'bottom';
}
move(neko, 0, 0);
}
}
neko.onmouseover = function() {
move(this, 0, 0);
rate(this, 0)
}
neko.onmouseout = function() {
move(this, nekoW / 2, nekoH / 2);
action(this);
}
neko.onmouseup = function() {
document.onmousemove = null;
this.style.transition = '.5s';
move(this, nekoW / 2, nekoH / 2);
action(this);
}
window.onresize = function() {
var bodyH = document.body.offsetHeight;
var nekoT = neko.offsetTop;
var bodyW = document.body.offsetWidth;
var nekoL = neko.offsetLeft;
if(nekoT + nekoH > bodyH) {
neko.style.top = bodyH - nekoH + 'px';
cuntH++;
}
if(bodyH > nekoT && cuntH > 0) {
neko.style.top = bodyH - nekoH + 'px';
}
if(nekoL + nekoW > bodyW) {
neko.style.left = bodyW - nekoW + 'px';
cuntW++;
}
if(bodyW > nekoL && cuntW > 0) {
neko.style.left = bodyW - nekoW + 'px';
}
move(neko, nekoW / 2, nekoH / 2);
}
</script>
</body>
</html>
这个效果和图片本身没有什么关系,所以什么图都可以实现。那么这期就到这里,下期再见。
今天我们来分享一个地图效果:
点击那个省,哪个省就会放大。那么我们来看代码部分:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=EDGE">
<title>ECharts China Map</title>
<style>
#china-map {
width: 1000px;
height: 1000px;
margin: auto;
}
#box {
display: none;
background-color: goldenrod;
width: 180px;
height: 30px;
}
#box-title {
display: block;
}
</style>
<script type="text/javascript" src="js/jquery.min.js"></script>
<script type="text/javascript" src="js/echarts.min.js"></script>
<script type="text/javascript" src="js/map/china.js"></script>
</head>
<body>
<button id="back">返回全国</button>
<div id="china-map"></div>
<script>
var myChart = echarts.init(document.getElementById('china-map'));
var oBack = document.getElementById("back");
var provinces = ['shanghai', 'hebei', 'shanxi', 'neimenggu', 'liaoning', 'jilin', 'heilongjiang', 'jiangsu', 'zhejiang', 'anhui', 'fujian', 'jiangxi', 'shandong', 'henan', 'hubei', 'hunan', 'guangdong', 'guangxi', 'hainan', 'sichuan', 'guizhou', 'yunnan', 'xizang', 'shanxi1', 'gansu', 'qinghai', 'ningxia', 'xinjiang', 'beijing', 'tianjin', 'chongqing', 'xianggang', 'aomen'];
var provincesText = ['上海', '河北', '山西', '内蒙古', '辽宁', '吉林', '黑龙江', '江苏', '浙江', '安徽', '福建', '江西', '山东', '河南', '湖北', '湖南', '广东', '广西', '海南', '四川', '贵州', '云南', '西藏', '陕西', '甘肃', '青海', '宁夏', '新疆', '北京', '天津', '重庆', '香港', '澳门'];
var seriesData = [{
name: '北京',
value: 100
}, {
name: '天津',
value: 0
}, {
name: '上海',
value: 60
}, {
name: '重庆',
value: 0
}, {
name: '河北',
value: 60
}, {
name: '河南',
value: 60
}, {
name: '云南',
value: 0
}, {
name: '辽宁',
value: 0
}, {
name: '黑龙江',
value: 0
}, {
name: '湖南',
value: 60
}, {
name: '安徽',
value: 0
}, {
name: '山东',
value: 60
}, {
name: '新疆',
value: 0
}, {
name: '江苏',
value: 0
}, {
name: '浙江',
value: 0
}, {
name: '江西',
value: 0
}, {
name: '湖北',
value: 60
}, {
name: '广西',
value: 60
}, {
name: '甘肃',
value: 0
}, {
name: '山西',
value: 60
}, {
name: '内蒙古',
value: 0
}, {
name: '陕西',
value: 0
}, {
name: '吉林',
value: 0
}, {
name: '福建',
value: 0
}, {
name: '贵州',
value: 0
}, {
name: '广东',
value: 597
}, {
name: '青海',
value: 0
}, {
name: '西藏',
value: 0
}, {
name: '四川',
value: 60
}, {
name: '宁夏',
value: 0
}, {
name: '海南',
value: 60
}, {
name: '台湾',
value: 0
}, {
name: '香港',
value: 0
}, {
name: '澳门',
value: 0
}];
oBack.onclick = function() {
initEcharts("china", "中国");
};
initEcharts("china", "中国");
// 初始化echarts
function initEcharts(pName, Chinese_) {
var tmpSeriesData = pName === "china" ? seriesData : [];
var option = {
title: {
text: Chinese_ || pName,
left: 'center'
},
tooltip: {
trigger: 'item',
formatter: '{b}<br/>{c} (p / km2)'
},
series: [{
name: Chinese_ || pName,
type: 'map',
mapType: pName,
roam: false, //是否开启鼠标缩放和平移漫游
data: tmpSeriesData,
top: "3%", //组件距离容器的距离
zoom: 1.1,
selectedMode: 'single',
label: {
normal: {
show: true, //显示省份标签
textStyle: {
color: "#fbfdfe"
} //省份标签字体颜色
},
emphasis: { //对应的鼠标悬浮效果
show: true,
textStyle: {
color: "#323232"
}
}
},
itemStyle: {
normal: {
borderWidth: .5, //区域边框宽度
borderColor: '#0550c3', //区域边框颜色
areaColor: "#4ea397", //区域颜色
},
emphasis: {
borderWidth: .5,
borderColor: '#4b0082',
areaColor: "#ece39e",
}
},
}]
};
myChart.setOption(option);
myChart.off("click");
if(pName === "china") { // 全国时,添加click 进入省级
myChart.on('click', function(param) {
console.log(param.name);
// 遍历取到provincesText 中的下标 去拿到对应的省js
for(var i = 0; i < provincesText.length; i++) {
if(param.name === provincesText[i]) {
//显示对应省份的方法
showProvince(provinces[i], provincesText[i]);
break;
}
}
if(param.componentType === 'series') {
var provinceName = param.name;
$('#box').css('display', 'block');
$("#box-title").html(provinceName);
}
});
} else { // 省份,添加双击 回退到全国
myChart.on("dblclick", function() {
initEcharts("china", "中国");
});
}
}
// 展示对应的省
function showProvince(pName, Chinese_) {
//这写省份的js都是通过在线构建工具生成的,保存在本地,需要时加载使用即可,最好不要一开始全部直接引入。
loadBdScript('$' + pName + 'JS', './js/map/province/' + pName + '.js', function() {
initEcharts(Chinese_);
});
}
// 加载对应的JS
function loadBdScript(scriptId, url, callback) {
var script = document.createElement("script");
script.type = "text/javascript";
if(script.readyState) { //IE
script.onreadystatechange = function() {
if(script.readyState === "loaded" || script.readyState === "complete") {
script.onreadystatechange = null;
callback();
}
};
} else { // Others
script.onload = function() {
callback();
};
}
script.src = url;
script.id = scriptId;
document.getElementsByTagName("head")[0].appendChild(script);
};
</script>
</body>
</html>
第二个页面
<!DOCTYPE html>
<html style="height: 90%">
<head>
<meta charset="utf-8">
</head>
<body style="width: 60%;height: 100%;margin-left: calc(20% - 2px);padding: 10px;border: 1px solid #e3e3e3;-webkit-border-radius: 4px;-moz-border-radius: 4px;border-radius: 4px;-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);">
<div id="main" style="height: 100%"></div>
<script type="text/javascript" src="./js/echarts.min.js"></script>
<!-- 引用echarts 3.0版本 -->
<script type="text/javascript" src="./js/map/china.js"></script>
<script type="text/javascript">
//初始化ECharts实例
var myChart = echarts.init(document.getElementById('main'));
var option = {
title: {
text: '北京',
link: 'http://echarts.baidu.com/',
subtext: '各区、县地图'
},
tooltip: {
trigger: 'item',
formatter: function(params) {
var res = '';
res += params['data'].name + '</br>';
res += '用户数' + ' : ' + params['data'].value2 + '</br>';
return res;
}
//formatter: '{b}<br/>{c}'
}, //鼠标放在地图上时,显示的内容及样式控制
visualMap: {
show: false, //色系条是否显示
min: 0,
max: 45000, //取其区间值就代表色系inRange中的一种颜色
left: 'left',
top: 'bottom',
text: ['高', '低'], // 文本,默认为数值文本
calculable: true,
inRange: {
color: ['#42a8be', '#00a69c', '#95ea95'], //上色范围
}
}, //给地图上色
series: [{
name: '北京市',
type: 'map',
mapType: 'beijing',
selectedMode: 'single',
label: {
normal: {
show: true
},
emphasis: {
show: true
}
},
itemStyle: {
normal: {
borderColor: '#fff',
areaColor: '#fff',
borderWidth: 2,
}, //正常样式
emphasis: {
areaColor: 'red',
borderWidth: 1,
borderColor: 'yellow',
} //鼠标事件区块样式
},
data: [{
name: '怀柔区',
value: 40500,
value2: 1024
},
{
name: '延庆区',
value: 23000,
value2: 1024
},
{
name: '顺义区',
value: 22500,
value2: 1024
},
{
name: '通州区',
value: 40500,
value2: 1024
},
{
name: '朝阳区',
value: 45000,
value2: 1024
},
{
name: '昌平区',
value: 37000,
value2: 1024
},
{
name: '门头沟区',
value: 40500,
value2: 1024
},
{
name: '石景山区',
value: 0,
value2: 1024
},
{
name: '海淀区',
value: 11161,
value2: 1024
},
{
name: '丰台区',
value: 15000,
value2: 1024
},
{
name: '房山区',
value: 20000,
value2: 1024
},
{
name: '密云区',
value: 25000,
value2: 1024
},
{
name: '平谷区',
value: 30000,
value2: 1024
},
{
name: '西城区',
value: 35000,
value2: 1024
},
{
name: '东城区',
value: 36000,
value2: 1024
},
{
name: '大兴区',
value: 45000,
value2: 1024
},
], //value的值是上面visualMap属性中设置的颜色色系区间的值,即0~45000
label: {
normal: {
show: true,
formatter: function(val) {
var area_content = '{a|' + val.name + '}' + '-' + '{b|' + val.data.value2 + '}';
return area_content.split("-").join(" ");
}, //让series 中的文字进行换行
rich: {
a: {
color: 'black'
},
b: {
color: 'yellow',
fontFamily: 'Microsoft YaHei',
fontSize: 14,
}
}, //富文本样式,就是上面的formatter中'{a|'和'{b|'
},
emphasis: {
show: true
}
}, //地图中文字内容及样式控制
}]
};
myChart.setOption(option, true);
myChart.on('click', function(params) {
alert(1);
console.log(params); //此处写点击事件内容
}); //点击事件,此事件还可以用到柱状图等其他地图
</script>
</body>
</html>
这期就到这里,下期见
本篇文章讲解如何在ssm(spring、springmvc、mybatis)结构的程序上集成sharding-jdbc(版本为2.0.3)进行分库分表;
假设分库分表行为如下:
将auth_user表分到4个库(user_0~user_3)中;
其他表不进行分库分表,保留在default_db库中;
1. POM配置
以spring配置文件为例,新增如下POM配置:
2 . 配置数据源
spring-datasource.xml配置所有需要的数据源如下–auth_user分库分表后需要的4个库user_0~user_3,以及不分库分表的默认库default_db:
properties配置文件内容如下:
3. 集成sharding数据源
spring-sharding.xml配置如下:
说明:spring-sharding.xml配置的分库分表规则:auth_user表分到id为sj_ds_${0..3}的四个库中,表名保持不变;其他表在id为sj_ds_default库中,不分库也不分表;集成sharding-jdbc的核心就是将SqlSessionFactoryBean需要的dataSource属性修改为shardingDataSource,把数据源交给sharding-jdbc处理;
另外,通过对比这里和sharding-jdbc1.5.4.1版本的配置请戳链接:https://www.jianshu.com/p/602e24845ed3,差异还是比较大,大概提现在如下一些地方:
namespace由rdb改为sharding;
默认数据库策略和默认表策略被设置为“节点的属性,分别是default-database-strategy-ref和default-table-strategy-ref;
默认数据源被设置为“节点的属性,即default-data-source-name;
“一些属性变更,例如:actual-tables改为actual-data-nodes,database-strategy改为database-strategy-ref;
分库逻辑AuthUserDatabaseShardingAlgorithm的代码很简单,源码如下:
这段代码参考sharding-jdbc源码中PreciseShardingAlgorithm.java接口的实现即可,例如PreciseModuloDatabaseShardingAlgorithm.java;这里和sharding-jdbc1.5.4.1版本的差异也比较大,sharding-jdbc1.5.4.1对于分库或者分表sharding算法实现的接口是不一样的,sharding-jdbc2.0.3将两者合二为一,且只有一个方法,即doSharding();
4. 注意事项
无法识别sharding-jdbc分库分表规则inline-expression问题,例如:
“
根本原因:
根本原因是spring把${}当做占位符,${0..3}这种表达式,spring会尝试去properties文件中找key为0..3的属性。但是这里是sharding-jdbc分库分表规则的inline表达式,需要spring忽略这种行为。否则会抛出异常:
java.lang.IllegalArgumentException: Could not resolve placeholder ‘0..3’ in value “sj_ds_${0..3}.auth_user”
解决办法:
配置: 或者:
5. Main测试
Main.java用来测试分库分表是否OK,其源码如下:
今天我们来分享一款js写的转盘抽奖程序:先看效果图
只能支持效果图。那么下面我们来分享代码部分:首先页面部分
<!DOCTYPE html>
<html >
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title></title>
<link href="css/zzsc.css" rel="stylesheet" type="text/css">
<style>
.container {
margin: 0 auto;
}
.content {
position: absolute;
width: 100%;
height: auto;
margin: 0px auto;
margin-top: 58%;
}
</style>
</head>
<body>
<img src="images/game-bg.jpg" alt="" style="position: absolute;width: 100%;height: 100%;">
<div class="content" style="margin-top: 58%;">
<img src="images/8.png" id="fen-img" style="display:none;" />
<img src="images/6.png" id="zuliao-img" style="display:none;" />
<img src="images/3.png" id="baowen-img" style="display:none;" />
<img src="images/5.png" id="yundou-img" style="display:none;" />
<img src="images/7.png" id="ganlan-img" style="display:none;" />
<img src="images/4.png" id="yusan-img" style="display:none;" />
<div class="banner">
<div class="turnplate" style="background-image:url(images/turnplate-bg.png);background-size:100% 100%;">
<canvas class="item" id="wheelcanvas" width="422px" height="422px"></canvas>
<img class="pointer" src="images/turnplate-pointer.png">
</div>
</div>
</div>
<div id="sbtn">
<a href="#">
<img src="images/btn-rule.png" alt="">
</a>
<a href="#">
<img src="images/btn-zjcx.png" alt="">
</a>
</div>
<script type="text/javascript" src="js/jquery.min.js"></script>
<script type="text/javascript" src="js/awardRotate.js"></script>
<script type="text/javascript">
var turnplate = {
restaraunts: [], //大转盘奖品名称
colors: [], //大转盘奖品区块对应背景颜色
outsideRadius: 192, //大转盘外圆的半径
textRadius: 155, //大转盘奖品位置距离圆心的距离
insideRadius: 68, //大转盘内圆的半径
startAngle: 0, //开始角度
bRotate: false //false:停止;ture:旋转
};
$(document).ready(function() {
//动态添加大转盘的奖品与奖品区域背景颜色
turnplate.restaraunts = ["20积分", "足疗机", "20积分", "保温杯", "20积分", "手持式电熨斗", "20积分 ", "橄榄油", "20积分", "晴雨伞"];
turnplate.colors = ["#FFF4D6", "#FFFFFF", "#FFF4D6", "#FFFFFF", "#FFF4D6", "#FFFFFF", "#FFF4D6", "#FFFFFF", "#FFF4D6", "#FFFFFF"];
var rotateTimeOut = function() {
$('#wheelcanvas').rotate({
angle: 0,
animateTo: 2160,
duration: 8000,
callback: function() {
alert('网络超时,请检查您的网络设置!');
}
});
};
//旋转转盘 item:奖品位置; txt:提示语;
var rotateFn = function(item, txt) {
var angles = item * (360 / turnplate.restaraunts.length) - (360 / (turnplate.restaraunts.length * 2));
if(angles < 270) {
angles = 270 - angles;
} else {
angles = 360 - angles + 270;
}
$('#wheelcanvas').stopRotate();
$('#wheelcanvas').rotate({
angle: 0,
animateTo: angles + 1800,
duration: 8000,
callback: function() {
//中奖提示
alert(txt);
turnplate.bRotate = !turnplate.bRotate;
}
});
};
$('.pointer').click(function() {
if(turnplate.bRotate) return;
turnplate.bRotate = !turnplate.bRotate;
//获取随机数(奖品个数范围内)
var item = rnd(1, turnplate.restaraunts.length);
//奖品数量等于10,指针落在对应奖品区域的中心角度[252, 216, 180, 144, 108, 72, 36, 360, 324, 288]
rotateFn(item, turnplate.restaraunts[item - 1]);
// switch (item) {
// case 1:
// rotateFn(252, turnplate.restaraunts[0]);
// break;
// case 2:
// rotateFn(216, turnplate.restaraunts[1]);
// break;
// case 3:
// rotateFn(180, turnplate.restaraunts[2]);
// break;
// case 4:
// rotateFn(144, turnplate.restaraunts[3]);
// break;
// case 5:
// rotateFn(108, turnplate.restaraunts[4]);
// break;
// case 6:
// rotateFn(72, turnplate.restaraunts[5]);
// break;
// case 7:
// rotateFn(36, turnplate.restaraunts[6]);
// break;
// case 8:
// rotateFn(360, turnplate.restaraunts[7]);
// break;
// case 9:
// rotateFn(324, turnplate.restaraunts[8]);
// break;
// case 10:
// rotateFn(288, turnplate.restaraunts[9]);
// break;
// }
console.log(item);
});
});
function rnd(n, m) {
n = 1; //最小随机数
m = 100; //最大随机数(概率范围最大值)
//最大数数不超过最大随机数
var ransluck = [50, 60, 65, 70, 75, 80, 85, 90, 95, 100]; //概率为比自己小的第一个数之间的差
var randoms = Math.floor(Math.random() * (m - n + 1) + n);
if(randoms <= ransluck[0]) {
var random = 1;
} else if(randoms <= ransluck[1]) {
var random = 2;
} else if(randoms <= ransluck[2]) {
var random = 3;
} else if(randoms <= ransluck[3]) {
var random = 4;
} else if(randoms <= ransluck[4]) {
var random = 5;
} else if(randoms <= ransluck[5]) {
var random = 6;
} else if(randoms <= ransluck[6]) {
var random = 7;
} else if(randoms <= ransluck[7]) {
var random = 8;
} else if(randoms <= ransluck[8]) {
var random = 9;
} else if(randoms <= ransluck[9]) {
var random = 10;
}
//alert(randoms);
//alert(random);
return random;
}
//页面所有元素加载完毕后执行drawRouletteWheel()方法对转盘进行渲染
window.onload = function() {
drawRouletteWheel();
};
function drawRouletteWheel() {
var canvas = document.getElementById("wheelcanvas");
if(canvas.getContext) {
//根据奖品个数计算圆周角度
var arc = Math.PI / (turnplate.restaraunts.length / 2);
var ctx = canvas.getContext("2d");
//在给定矩形内清空一个矩形
ctx.clearRect(0, 0, 422, 422);
//strokeStyle 属性设置或返回用于笔触的颜色、渐变或模式
ctx.strokeStyle = "#FFBE04";
//font 属性设置或返回画布上文本内容的当前字体属性
ctx.font = '16px Microsoft YaHei';
for(var i = 0; i < turnplate.restaraunts.length; i++) {
var angle = turnplate.startAngle + i * arc;
ctx.fillStyle = turnplate.colors[i];
ctx.beginPath();
//arc(x,y,r,起始角,结束角,绘制方向) 方法创建弧/曲线(用于创建圆或部分圆)
ctx.arc(211, 211, turnplate.outsideRadius, angle, angle + arc, false);
ctx.arc(211, 211, turnplate.insideRadius, angle + arc, angle, true);
ctx.stroke();
ctx.fill();
//锁画布(为了保存之前的画布状态)
ctx.save();
//----绘制奖品开始----
ctx.fillStyle = "#E5302F";
var text = turnplate.restaraunts[i];
var line_height = 17;
//translate方法重新映射画布上的 (0,0) 位置
ctx.translate(211 + Math.cos(angle + arc / 2) * turnplate.textRadius, 211 + Math.sin(angle + arc / 2) * turnplate.textRadius);
//rotate方法旋转当前的绘图
ctx.rotate(angle + arc / 2 + Math.PI / 2);
/** 下面代码根据奖品类型、奖品名称长度渲染不同效果,如字体、颜色、图片效果。(具体根据实际情况改变) **/
if(text.indexOf("M") > 0) { //流量包
var texts = text.split("M");
for(var j = 0; j < texts.length; j++) {
ctx.font = j == 0 ? 'bold 20px Microsoft YaHei' : '16px Microsoft YaHei';
if(j == 0) {
ctx.fillText(texts[j] + "M", -ctx.measureText(texts[j] + "M").width / 2, j * line_height);
} else {
ctx.fillText(texts[j], -ctx.measureText(texts[j]).width / 2, j * line_height);
}
}
} else if(text.indexOf("M") == -1 && text.length > 6) { //奖品名称长度超过一定范围
text = text.substring(0, 6) + "||" + text.substring(6);
var texts = text.split("||");
for(var j = 0; j < texts.length; j++) {
ctx.fillText(texts[j], -ctx.measureText(texts[j]).width / 2, j * line_height);
}
} else {
//在画布上绘制填色的文本。文本的默认颜色是黑色
//measureText()方法返回包含一个对象,该对象包含以像素计的指定字体宽度
ctx.fillText(text, -ctx.measureText(text).width / 2, 0);
}
//添加对应图标
if(text.indexOf("分") > 0) {
var img = document.getElementById("fen-img");
img.onload = function() {
ctx.drawImage(img, -15, 10);
};
ctx.drawImage(img, -15, 10);
} else if(text.indexOf("足疗") >= 0) {
var img = document.getElementById("zuliao-img");
img.onload = function() {
ctx.drawImage(img, -15, 10);
};
ctx.drawImage(img, -15, 10);
} else if(text.indexOf("保温") >= 0) {
var img = document.getElementById("baowen-img");
img.onload = function() {
ctx.drawImage(img, -15, 10);
};
ctx.drawImage(img, -15, 10);
} else if(text.indexOf("电熨斗") >= 0) {
var img = document.getElementById("yundou-img");
img.onload = function() {
ctx.drawImage(img, -15, 10);
};
ctx.drawImage(img, -15, 10);
} else if(text.indexOf("橄榄") >= 0) {
var img = document.getElementById("ganlan-img");
img.onload = function() {
ctx.drawImage(img, -15, 10);
};
ctx.drawImage(img, -15, 10);
} else if(text.indexOf("雨伞") >= 0) {
var img = document.getElementById("yusan-img");
img.onload = function() {
ctx.drawImage(img, -15, 10);
};
ctx.drawImage(img, -15, 10);
}
//把当前画布返回(调整)到上一个save()状态之前
ctx.restore();
//----绘制奖品结束----
}
}
}
</script>
</body>
</html>
然后样式部分:
body, ul, ol, li, p, h1, h2, h3, h4, h5, h6, form, fieldset, table, td, img, div {
margin: 0;
padding: 0;
border: 0;
}
/*body{color:#333; font-size:12px;font-family:"Microsoft YaHei"}*/
ul, ol {
list-style-type: none;
}
select, input, img, select {
vertical-align: middle;
}
input {
font-size: 12px;
}
.clear {
clear: both;
}
/* 大转盘样式 */
.banner {
display: block;
width: 79%;
margin-left: auto;
margin-right: auto;
margin-bottom: 20px;
}
.banner .turnplate {
display: block;
width: 100%;
position: relative;
}
.banner .turnplate canvas.item {
width: 100%;
}
.banner .turnplate img.pointer {
position: absolute;
width: 31.5%;
height: 42.5%;
left: 34.1%;
top: 23%;
}
#sbtn {
position: absolute;
top: 85%;
text-align: center;
width: 100%;
}
#sbtn img {
width: 24%;
margin: 0 20px;
}
然后插件脚本部分:
(function($) {
var supportedCSS,styles=document.getElementsByTagName("head")[0].style,toCheck="transformProperty WebkitTransform OTransform msTransform MozTransform".split(" ");
for (var a=0;a<toCheck.length;a++) if (styles[toCheck[a]] !== undefined) supportedCSS = toCheck[a];
var IE = eval('"v"=="v"');
jQuery.fn.extend({
rotate:function(parameters)
{
if (this.length===0||typeof parameters=="undefined") return;
if (typeof parameters=="number") parameters={angle:parameters};
var returned=[];
for (var i=0,i0=this.length;i<i0;i++)
{
var element=this.get(i);
if (!element.Wilq32 || !element.Wilq32.PhotoEffect) {
var paramClone = $.extend(true, {}, parameters);
var newRotObject = new Wilq32.PhotoEffect(element,paramClone)._rootObj;
returned.push($(newRotObject));
}
else {
element.Wilq32.PhotoEffect._handleRotation(parameters);
}
}
return returned;
},
getRotateAngle: function(){
var ret = [];
for (var i=0,i0=this.length;i<i0;i++)
{
var element=this.get(i);
if (element.Wilq32 && element.Wilq32.PhotoEffect) {
ret[i] = element.Wilq32.PhotoEffect._angle;
}
}
return ret;
},
stopRotate: function(){
for (var i=0,i0=this.length;i<i0;i++)
{
var element=this.get(i);
if (element.Wilq32 && element.Wilq32.PhotoEffect) {
clearTimeout(element.Wilq32.PhotoEffect._timer);
}
}
}
});
Wilq32=window.Wilq32||{};
Wilq32.PhotoEffect=(function(){
if (supportedCSS) {
return function(img,parameters){
img.Wilq32 = {
PhotoEffect: this
};
this._img = this._rootObj = this._eventObj = img;
this._handleRotation(parameters);
}
} else {
return function(img,parameters) {
this._img = img;
this._rootObj=document.createElement('span');
this._rootObj.style.display="inline-block";
this._rootObj.Wilq32 =
{
PhotoEffect: this
};
img.parentNode.insertBefore(this._rootObj,img);
if (img.complete) {
this._Loader(parameters);
} else {
var self=this;
// TODO: Remove jQuery dependency
jQuery(this._img).bind("load", function()
{
self._Loader(parameters);
});
}
}
}
})();
Wilq32.PhotoEffect.prototype={
_setupParameters : function (parameters){
this._parameters = this._parameters || {};
if (typeof this._angle !== "number") this._angle = 0 ;
if (typeof parameters.angle==="number") this._angle = parameters.angle;
this._parameters.animateTo = (typeof parameters.animateTo==="number") ? (parameters.animateTo) : (this._angle);
this._parameters.step = parameters.step || this._parameters.step || null;
this._parameters.easing = parameters.easing || this._parameters.easing || function (x, t, b, c, d) { return -c * ((t=t/d-1)*t*t*t - 1) + b; }
this._parameters.duration = parameters.duration || this._parameters.duration || 1000;
this._parameters.callback = parameters.callback || this._parameters.callback || function(){};
if (parameters.bind && parameters.bind != this._parameters.bind) this._BindEvents(parameters.bind);
},
_handleRotation : function(parameters){
this._setupParameters(parameters);
if (this._angle==this._parameters.animateTo) {
this._rotate(this._angle);
}
else {
this._animateStart();
}
},
_BindEvents:function(events){
if (events && this._eventObj)
{
// Unbinding previous Events
if (this._parameters.bind){
var oldEvents = this._parameters.bind;
for (var a in oldEvents) if (oldEvents.hasOwnProperty(a))
// TODO: Remove jQuery dependency
jQuery(this._eventObj).unbind(a,oldEvents[a]);
}
this._parameters.bind = events;
for (var a in events) if (events.hasOwnProperty(a))
// TODO: Remove jQuery dependency
jQuery(this._eventObj).bind(a,events[a]);
}
},
_Loader:(function()
{
if (IE)
return function(parameters)
{
var width=this._img.width;
var height=this._img.height;
this._img.parentNode.removeChild(this._img);
this._vimage = this.createVMLNode('image');
this._vimage.src=this._img.src;
this._vimage.style.height=height+"px";
this._vimage.style.width=width+"px";
this._vimage.style.position="absolute"; // FIXES IE PROBLEM - its only rendered if its on absolute position!
this._vimage.style.top = "0px";
this._vimage.style.left = "0px";
/* Group minifying a small 1px precision problem when rotating object */
this._container = this.createVMLNode('group');
this._container.style.width=width;
this._container.style.height=height;
this._container.style.position="absolute";
this._container.setAttribute('coordsize',width-1+','+(height-1)); // This -1, -1 trying to fix ugly problem with small displacement on IE
this._container.appendChild(this._vimage);
this._rootObj.appendChild(this._container);
this._rootObj.style.position="relative"; // FIXES IE PROBLEM
this._rootObj.style.width=width+"px";
this._rootObj.style.height=height+"px";
this._rootObj.setAttribute('id',this._img.getAttribute('id'));
this._rootObj.className=this._img.className;
this._eventObj = this._rootObj;
this._handleRotation(parameters);
}
else
return function (parameters)
{
this._rootObj.setAttribute('id',this._img.getAttribute('id'));
this._rootObj.className=this._img.className;
this._width=this._img.width;
this._height=this._img.height;
this._widthHalf=this._width/2; // used for optimisation
this._heightHalf=this._height/2;// used for optimisation
var _widthMax=Math.sqrt((this._height)*(this._height) + (this._width) * (this._width));
this._widthAdd = _widthMax - this._width;
this._heightAdd = _widthMax - this._height; // widthMax because maxWidth=maxHeight
this._widthAddHalf=this._widthAdd/2; // used for optimisation
this._heightAddHalf=this._heightAdd/2;// used for optimisation
this._img.parentNode.removeChild(this._img);
this._aspectW = ((parseInt(this._img.style.width,10)) || this._width)/this._img.width;
this._aspectH = ((parseInt(this._img.style.height,10)) || this._height)/this._img.height;
this._canvas=document.createElement('canvas');
this._canvas.setAttribute('width',this._width);
this._canvas.style.position="relative";
this._canvas.style.left = -this._widthAddHalf + "px";
this._canvas.style.top = -this._heightAddHalf + "px";
this._canvas.Wilq32 = this._rootObj.Wilq32;
this._rootObj.appendChild(this._canvas);
this._rootObj.style.width=this._width+"px";
this._rootObj.style.height=this._height+"px";
this._eventObj = this._canvas;
this._cnv=this._canvas.getContext('2d');
this._handleRotation(parameters);
}
})(),
_animateStart:function()
{
if (this._timer) {
clearTimeout(this._timer);
}
this._animateStartTime = +new Date;
this._animateStartAngle = this._angle;
this._animate();
},
_animate:function()
{
var actualTime = +new Date;
var checkEnd = actualTime - this._animateStartTime > this._parameters.duration;
// TODO: Bug for animatedGif for static rotation ? (to test)
if (checkEnd && !this._parameters.animatedGif)
{
clearTimeout(this._timer);
}
else
{
if (this._canvas||this._vimage||this._img) {
var angle = this._parameters.easing(0, actualTime - this._animateStartTime, this._animateStartAngle, this._parameters.animateTo - this._animateStartAngle, this._parameters.duration);
this._rotate((~~(angle*10))/10);
}
if (this._parameters.step) {
this._parameters.step(this._angle);
}
var self = this;
this._timer = setTimeout(function()
{
self._animate.call(self);
}, 10);
}
// To fix Bug that prevents using recursive function in callback I moved this function to back
if (this._parameters.callback && checkEnd){
this._angle = this._parameters.animateTo;
this._rotate(this._angle);
this._parameters.callback.call(this._rootObj);
}
},
_rotate : (function()
{
var rad = Math.PI/180;
if (IE)
return function(angle)
{
this._angle = angle;
this._container.style.rotation=(angle%360)+"deg";
}
else if (supportedCSS)
return function(angle){
this._angle = angle;
this._img.style[supportedCSS]="rotate("+(angle%360)+"deg)";
}
else
return function(angle)
{
this._angle = angle;
angle=(angle%360)* rad;
// clear canvas
this._canvas.width = this._width+this._widthAdd;
this._canvas.height = this._height+this._heightAdd;
// REMEMBER: all drawings are read from backwards.. so first function is translate, then rotate, then translate, translate..
this._cnv.translate(this._widthAddHalf,this._heightAddHalf); // at least center image on screen
this._cnv.translate(this._widthHalf,this._heightHalf); // we move image back to its orginal
this._cnv.rotate(angle); // rotate image
this._cnv.translate(-this._widthHalf,-this._heightHalf); // move image to its center, so we can rotate around its center
this._cnv.scale(this._aspectW,this._aspectH); // SCALE - if needed ;)
this._cnv.drawImage(this._img, 0, 0); // First - we draw image
}
})()
}
if (IE)
{
Wilq32.PhotoEffect.prototype.createVMLNode=(function(){
document.createStyleSheet().addRule(".rvml", "behavior:url(#default#VML)");
try {
!document.namespaces.rvml && document.namespaces.add("rvml", "urn:schemas-microsoft-com:vml");
return function (tagName) {
return document.createElement('<rvml:' + tagName + '>');
};
} catch (e) {
return function (tagName) {
return document.createElement('<' + tagName + ' xmlns="urn:schemas-microsoft.com:vml">');
};
}
})();
}
})(jQuery);
Array类的使用
java.lang.Array是对Java反射包中数组操作的一个类。JavaSE8的文档中对Array的描述是这样说的:
The Array class provides static methods to dynamically create and access Java arrays.
Array类提供静态方法来动态创建和访问Java数组。访问不难理解,动态创建可以细看一下。
让我们先看看java.util.Arrays
注意是Arrays,相信有些小伙伴已经用过很多次这个工具类了,提供了很多对数组操作的方法方便我们使用。
上面说了java.lang.Array是提供给我们静态方法来动态创建和访问数组。让我们来看看Arrays中的copyOf方式是怎么来动态操作数组的吧。
copyOf是拿来干嘛的呢?Arrays主要提供这个方法来给已经填满的数组来拓展数组大小的。
你可以这样用
不知道大家有没有注意到,这个方法是个泛型的返回结果。它的第一个参数是原始数组,第二个参数为新的长度,返回的是调用了另一个重载的copyOf方法,让我们来看看这个重载的copyOf方法吧。
里面的调用不难理解,就是如果传进来的original对象数组的Class和Object[]的Class相等那就直接new Object[]如果不相等就调用java.lang.reflect.Array中的newInstance方法进行创建新数组,后面调用的是System.arraycopy方法的作用源码中的注释是:Copies an array from the specified source array, beginning at the specified position, to the specified position of the destination array. 意思是:从指定的数组的制定位置开始复制到目标数组的指定位置。
为什么要用反射实现数组的扩展
我们来看一下不用反射实现的"copyOf"
如果没有上面那个Arrays的copyOf方法可能很多人会直接潇潇洒洒写出如上代码。不过有没有想过一个问题,他能不能转型成对应的你想用的类?这样说,一个MyObject[]类转成Object[],然后再转回来是可以的,但是从一开始就是Object[]的数组是不能转成MyObject[],这样做会抛出ClassCastException异常,这是因为这个数组是用new Object[length]创建的,Java数组在创建的时候回记住每个元素的类型,就是在new的时候的类型。
那么怎样我们才可以强转呢?看如下代码
看了上面代码,有的小伙伴会有疑问,为什么要用object接收数组对象,这是因为基本数据类型的数组不能传给对象数组,但是可以转成对象
访问数组内的对象
Array类提供了一些方法可以供我们使用
完整代码如下
今天我们来分享是一款用jquery实现的手机解锁界面,非常好看,下面我们先看图
我们先来看html部分代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>jQuery手机屏幕手势解锁代码 </title>
<!--字体-->
<!--动画库-->
<link rel='stylesheet' href='css/animate.min.css'>
<!--图标库-->
<link rel='stylesheet' href='https://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css'>
<!--解锁-->
<link rel='stylesheet' href='css/pattern-lock.min.css'>
<!--页面布局-->
<link rel="stylesheet" href="css/jiesuo.css">
</head>
<body>
<div class="mhn-ui-wrap">
<div class="mhn-ui-page page-lock">
<div class="mhn-ui-date-time">
<div class="mhn-ui-time">6:02 PM</div>
<div class="mhn-ui-day">星期三</div>
<div class="mhn-ui-date">10月 10, 2019</div>
</div>
<div class="mhn-lock-wrap">
<div class="mhn-lock-title" data-title="绘制图案解锁"></div>
<div class="mhn-lock"></div>
</div>
</div>
<div class="mhn-ui-page page-home">
<div class="mhn-ui-app-time"> </div>
<div class="mhn-ui-app-title-head">
<span class="mhn-ui-page-title">All Application</span>
<div class="mhn-ui-filter">
<span class="mhn-ui-btn ion-funnel"></span>
<div class="mhn-ui-filter-list">
<div data-filter="all" class="active">All Application</div>
<div data-filter="general">General Application</div>
<div data-filter="social">Social Application</div>
<div data-filter="credits">Credits Application</div>
</div>
</div>
</div>
<div class="mhn-ui-row mhn-ui-apps">
<div class="mhn-ui-col" data-filter="general">
<div class="mhn-ui-icon" data-open="page-author">
<span class="ion-person" data-color="#2980b9"></span>
<div class="mhn-ui-icon-title">Author</div>
</div>
</div>
<div class="mhn-ui-col" data-filter="general">
<div class="mhn-ui-icon" data-open="page-contact">
<span class="ion-chatbox" data-color="#8e44ad"></span>
<div class="mhn-ui-icon-title">Contact</div>
</div>
</div>
<div class="mhn-ui-col" data-filter="general">
<div class="mhn-ui-icon" data-href="#">
<span class="ion-ios-briefcase" data-color="#f39c12"></span>
<div class="mhn-ui-icon-title">Portfolio</div>
</div>
</div>
<div class="mhn-ui-col" data-filter="general">
<div class="mhn-ui-icon" data-open="page-credits">
<span class="ion-information-circled" data-color="#16a085"></span>
<div class="mhn-ui-icon-title">Credits</div>
</div>
</div>
<div class="mhn-ui-col" data-filter="social">
<div class="mhn-ui-icon" data-href="#">
<span class="ion-social-facebook" data-color="#3b5998"></span>
<div class="mhn-ui-icon-title">Facebook</div>
</div>
</div>
<div class="mhn-ui-col" data-filter="social">
<div class="mhn-ui-icon" data-href="#">
<span class="ion-social-twitter" data-color="#56a3d9"></span>
<div class="mhn-ui-icon-title">Twitter</div>
</div>
</div>
<div class="mhn-ui-col" data-filter="credits">
<div class="mhn-ui-icon" data-href="#">
<span class="ion-social-javascript" data-color="#6639b6"></span>
<div class="mhn-ui-icon-title">jQuery</div>
</div>
</div>
<div class="mhn-ui-col" data-filter="credits">
<div class="mhn-ui-icon" data-href="#">
<span class="ion-ionic" data-color="#3e50b4"></span>
<div class="mhn-ui-icon-title">Ionicons</div>
</div>
</div>
<div class="mhn-ui-col" data-filter="credits">
<div class="mhn-ui-icon" data-href="#">
<span class="ion-social-css3-outline" data-color="#785447"></span>
<div class="mhn-ui-icon-title">Animate</div>
</div>
</div>
<div class="mhn-ui-col" data-filter="credits">
<div class="mhn-ui-icon" data-href="#">
<span class="ion-android-camera" data-color="#000000"></span>
<div class="mhn-ui-icon-title">Unsplash</div>
</div>
</div>
<div class="mhn-ui-col" data-filter="credits">
<div class="mhn-ui-icon" data-href="#">
<span class="ion-android-unlock" data-color="#4bae4f"></span>
<div class="mhn-ui-icon-title">patternLock</div>
</div>
</div>
</div>
<div class="mhn-ui-bottom-link-bar">
<span class="mhn-ui-bottom-btn ion-ios-home"></span>
<span class="mhn-ui-bottom-btn ion-ios-locked" onClick="mhnUI.page.show('page-lock')"></span>
</div>
</div>
<div class="mhn-ui-page page-author">
<div class="mhn-ui-app-time"></div>
<div class="mhn-ui-app-title-head">
<span class="ion-person"></span> Author</div>
<div class="text-center">
<img class="flipInX animated" src="img/t.png">
</div>
<p class="text-center">Hi, It's me Mohan. I'm a web and graphics designer. Designing is my passion and I have been working on various designing projects.</p>
<div class="mhn-ui-bottom-link-bar">
<span class="mhn-ui-bottom-btn ion-ios-home" onClick="mhnUI.page.show('page-home')"></span>
<span class="mhn-ui-bottom-btn ion-ios-locked" onClick="mhnUI.page.show('page-lock')"></span>
</div>
</div>
<div class="mhn-ui-page page-contact">
<div class="mhn-ui-app-time"> </div>
<div class="mhn-ui-app-title-head">
<span class="ion-chatbox"></span> Contact</div>
<div class="mhn-ui-bottom-link-bar">
<span class="mhn-ui-bottom-btn ion-ios-home" onClick="mhnUI.page.show('page-home')"></span>
<span class="mhn-ui-bottom-btn ion-ios-locked" onClick="mhnUI.page.show('page-lock')"></span>
</div>
</div>
<div class="mhn-ui-page page-credits">
<div class="mhn-ui-app-time"> </div>
<div class="mhn-ui-app-title-head">
<span class="ion-information-circled"></span> Credits</div>
<div class="mhn-ui-credit-list">
<div class="mhn-ui-credit">
<p>jQuery is a fast, small, and feature-rich JavaScript library.</p>
</div>
</div>
<div class="mhn-ui-bottom-link-bar">
<span class="mhn-ui-bottom-btn ion-ios-home" onClick="mhnUI.page.show('page-home')"></span>
<span class="mhn-ui-bottom-btn ion-ios-locked" onClick="mhnUI.page.show('page-lock')"></span>
</div>
</div>
<div class="mhn-ui-dialog-wrap">
<div class="mhn-ui-dialog">
<div class="mhn-ui-dialog-title">Are you sure?</div>
<p>This application wants to open an external link. To confirm, please click on yes button.</p>
<a data-action="confirm" class="mhn-ui-dialog-btn">Yes</a>
<a data-action="cancel" class="mhn-ui-dialog-btn">No</a>
</div>
</div>
</div>
<div class="mhn-ui-info">绘制'Z'形状开始解锁:)</div>
<script src="js/jquery.min.js"></script>
<script src="js/jiesuo.js"></script>
</body>
</html>>
接下来放出css部分的代码
.patt-holder {
background: #3382c0;
-ms-touch-action: none
}
.patt-wrap {
position: relative;
cursor: pointer
}
.patt-wrap li, .patt-wrap ul {
list-style: none;
margin: 0;
padding: 0
}
.patt-circ {
position: relative;
float: left;
box-sizing: border-box;
-moz-box-sizing: border-box
}
.patt-circ.hovered {
border: 3px solid #090
}
.patt-error .patt-circ.hovered {
border: 3px solid #BA1B26
}
.patt-hidden .patt-circ.hovered {
border: 0
}
.patt-dots, .patt-lines {
border-radius: 5px;
height: 10px;
position: absolute
}
.patt-dots {
background: #FFF;
width: 10px;
top: 50%;
left: 50%;
margin-top: -5px;
margin-left: -5px
}
.patt-lines {
background: rgba(255, 255, 255, .7);
transform-origin: 5px 5px;
-ms-transform-origin: 5px 5px;
-webkit-transform-origin: 5px 5px
}
.patt-hidden .patt-lines {
display: none
}
.mhn-ui-date-time, .text-center {
text-align: center
}
*, :after, :before {
box-sizing: border-box
}
.pull-left {
float: left
}
.pull-right {
float: right
}
.clearfix:after, .clearfix:before {
content: '';
display: table
}
.clearfix:after {
clear: both;
display: block
}
body {
margin: 0;
color: #fff;
background-color: #FD6969;
font: 300 14px/18px Roboto, sans-serif
}
a {
color: inherit;
text-decoration: none
}
a:hover {
text-decoration: underline
}
.mhn-ui-wrap {
width: 300px;
height: 475px;
overflow: hidden;
position: relative;
margin: 30px auto 0;
background: url(../img/bg.png) center no-repeat #2c3e50;
box-shadow: 0 17px 50px 0 rgba(0, 0, 0, .19), 0 12px 15px 0 rgba(0, 0, 0, .24)
}
.mhn-ui-wrap:before {
top: 0;
left: 0;
right: 0;
bottom: 0;
content: '';
position: absolute;
background: rgba(0, 0, 0, .4)
}
.mhn-ui-date-time {
color: #eee;
z-index: 100;
position: relative
}
.mhn-ui-date-time .mhn-ui-time {
font-size: 28px;
font-weight: 400;
margin-bottom: 15px
}
.mhn-ui-date-time .mhn-ui-day {
font-size: 24px;
margin-bottom: 10px
}
.mhn-ui-date-time .mhn-ui-date {
font-size: 18px;
font-weight: 400
}
.mhn-ui-app-time {
padding: 0 5px;
font-size: 12px;
text-align: right;
margin: -15px -15px auto;
background: rgba(0, 0, 0, .6)
}
.mhn-lock-wrap {
left: 0;
right: 0;
bottom: 0;
z-index: 100;
position: absolute
}
.mhn-lock-wrap .mhn-lock-title {
text-align: center;
text-shadow: 0 1px 1px rgba(0, 0, 0, .5)
}
.mhn-lock-wrap .mhn-lock-success {
color: transparent;
text-shadow: none
}
.mhn-lock-wrap .mhn-lock-failure {
color: #f34235
}
.mhn-lock {
margin: auto;
background: 0 0
}
.patt-wrap {
margin: auto;
overflow: hidden
}
.patt-wrap li {
transition: all .4s ease-in-out 0s
}
.patt-dots, .patt-lines {
transition: background .1s ease-in-out 0s
}
.patt-circ {
border: 3px solid transparent
}
.patt-dots {
background: rgba(255, 255, 255, .8)
}
.patt-lines {
background: rgba(255, 255, 255, .4)
}
.patt-circ.hovered {
border-color: #ddd;
background: rgba(255, 255, 255, .2)
}
.patt-error .patt-circ.hovered {
background: rgba(243, 66, 53, .4);
border-color: rgba(243, 66, 53, .8)
}
.patt-error .patt-lines {
background: rgba(243, 66, 53, .5)
}
.patt-success .patt-circ.hovered {
background: rgba(75, 174, 79, .4);
border-color: rgba(75, 174, 79, .8)
}
.patt-success .patt-lines {
background: rgba(75, 174, 79, .5)
}
.mhn-ui-page {
height: 100%;
z-index: 200;
display: none;
padding: 15px;
position: relative
}
.mhn-ui-page.page-lock {
position: initial
}
.mhn-ui-page .mhn-ui-app-title-head {
padding: 15px;
font-size: 16px;
margin: 0 -15px;
background: rgba(0, 0, 0, .4)
}
.mhn-ui-page .mhn-ui-filter {
float: right;
position: relative
}
.mhn-ui-page .mhn-ui-filter .mhn-ui-btn {
right: 0;
top: -5px;
padding: 5px;
cursor: pointer;
position: absolute;
display: inline-block
}
.mhn-ui-page .mhn-ui-filter .mhn-ui-btn.active {
background: teal
}
.mhn-ui-page .mhn-ui-filter-list {
right: 0;
top: 20px;
padding: 5px;
width: 180px;
display: none;
position: absolute;
background: rgba(0, 0, 0, .8)
}
.mhn-ui-page .mhn-ui-filter-list>div {
display: block;
font-size: 14px;
cursor: pointer;
padding: 2px 4px
}
.mhn-ui-page .mhn-ui-filter-list>div.active {
color: teal
}
.mhn-ui-page .mhn-ui-filter-list>div:hover {
background: teal
}
.mhn-ui-page .mhn-ui-filter-list>div.active:hover {
background: 0 0
}
.mhn-ui-page .mhn-ui-row {
margin-top: 15px
}
.mhn-ui-page .mhn-ui-row:after, .mhn-ui-page .mhn-ui-row:before {
content: '';
display: table
}
.mhn-ui-page .mhn-ui-row:after {
clear: both;
display: block
}
.mhn-ui-page .mhn-ui-col {
width: 25%;
float: left;
margin-bottom: 15px
}
.mhn-ui-bottom-link-bar {
left: 0;
right: 0;
bottom: 0;
padding: 15px;
position: absolute;
text-align: center
}
.mhn-ui-bottom-link-bar .mhn-ui-bottom-btn {
width: 40px;
height: 40px;
cursor: pointer;
font-size: 28px;
line-height: 40px;
text-align: center;
border-radius: 50%;
display: inline-block
}
.mhn-ui-bottom-link-bar .mhn-ui-bottom-btn:nth-child(1) {
margin-right: 15px
}
.mhn-ui-bottom-link-bar .mhn-ui-bottom-btn:nth-child(2) {
margin-left: 15px
}
.mhn-ui-bottom-link-bar .mhn-ui-bottom-btn:hover {
color: #ccc;
background: rgba(0, 0, 0, .8)
}
.mhn-ui-icon {
text-align: center
}
.mhn-ui-icon span {
width: 55px;
height: 55px;
margin: auto;
display: block;
font-size: 28px;
cursor: pointer;
line-height: 55px;
text-align: center;
border-radius: 15px;
background: rgba(0, 0, 0, .3);
transition: background .4s ease-in-out 0s;
box-shadow: 0 -1px 0 rgba(255, 255, 255, .5) inset
}
.mhn-ui-icon .mhn-ui-icon-title {
margin-top: 5px;
cursor: default;
overflow: hidden;
font-size: 13px;
text-overflow: ellipsis;
text-shadow: 0 1px 1px rgba(0, 0, 0, .5)
}
.mhn-ui-page.page-author img {
padding: 8px;
margin-top: 15px;
border-radius: 50%;
background: rgba(255, 255, 255, .7)
}
.mhn-ui-credit {
padding: 5px;
font-size: 13px;
margin-top: 15px;
background: rgba(0, 0, 0, .2);
border: 1px solid rgba(0, 0, 0, .2)
}
.mhn-ui-credit p {
margin: 0;
color: #aaa
}
.mhn-ui-credit a {
font-weight: 500
}
.mhn-ui-dialog-wrap {
top: 0;
left: 0;
right: 0;
bottom: 0;
display: none;
z-index: 1000;
position: absolute;
background: rgba(0, 0, 0, .7)
}
.mhn-ui-dialog {
padding: 15px;
background: #000;
margin: 50% 0 auto
}
.mhn-ui-dialog .mhn-ui-dialog-title {
font-size: 18px;
font-weight: 500
}
.mhn-ui-dialog .mhn-ui-dialog-btn {
padding: 5px;
min-width: 65px;
cursor: pointer;
margin-right: 10px;
text-align: center;
display: inline-block;
border: 2px solid rgba(255, 255, 255, .8)
}
.mhn-ui-dialog .mhn-ui-dialog-btn:hover {
background: #009688;
text-decoration: none
}
.mhn-ui-info {
margin: 30px 0;
font-size: 16px;
text-align: center
}
.mhn-ui-date, .mhn-ui-time {
-webkit-animation: zoomIn 1s;
animation: zoomIn 1s
}
.mhn-ui-day {
-webkit-animation: rubberBand 1s;
animation: rubberBand 1s
}
.mhn-lock-failure {
-webkit-animation: zoomIn .4s;
animation: zoomIn .4s
}
.patt-circ:nth-child(1), .patt-circ:nth-child(2), .patt-circ:nth-child(3) {
-webkit-animation: fadeInUp .4s;
animation: fadeInUp .4s
}
.patt-circ:nth-child(4), .patt-circ:nth-child(5), .patt-circ:nth-child(6) {
-webkit-animation: fadeInUp .6s;
animation: fadeInUp .6s
}
.patt-circ:nth-child(7), .patt-circ:nth-child(8), .patt-circ:nth-child(9) {
-webkit-animation: fadeInUp .8s;
animation: fadeInUp .8s
}
.mhn-ui-icon span {
-webkit-animation: zoomIn .6s;
animation: zoomIn .6s
}
.mhn-ui-bottom-btn {
-webkit-animation: bounceInUp .8s;
animation: bounceInUp .8s
}
.mhn-ui-credit-list .mhn-ui-credit:nth-child(1) {
-webkit-animation: fadeInUp .4s;
animation: fadeInUp .4s
}
.mhn-ui-credit-list .mhn-ui-credit:nth-child(2) {
-webkit-animation: fadeInUp .5s;
animation: fadeInUp .5s
}
.mhn-ui-credit-list .mhn-ui-credit:nth-child(3) {
-webkit-animation: fadeInUp .6s;
animation: fadeInUp .6s
}
.mhn-ui-credit-list .mhn-ui-credit:nth-child(4) {
-webkit-animation: fadeInUp .7s;
animation: fadeInUp .7s
}
.mhn-ui-credit-list .mhn-ui-credit:nth-child(5) {
-webkit-animation: fadeInUp .8s;
animation: fadeInUp .8s
}
最后脚本部分代码
$(function() {
mhnUI.setup();
});
mhnUI = {
pattern: "",
setup: function() {
this.lock(), this.filter(), this.colors(), this.links.setup(), this.dialog.setup(), setInterval("mhnUI.datetime()", 1e3)
},
lock: function() {
mhnUI.page.hide(), pattern = new PatternLock(".mhn-lock", {
margin: 15
}), $(".mhn-lock-title").html($(".mhn-lock-title").data("title")), pattern.checkForPattern("1235789", function() {
$(".mhn-lock-title").html('<span>Yes! you unlocked pattern</span>'), $(".patt-holder").addClass("patt-success"), setTimeout(function() {
pattern.reset(), mhnUI.message()
}, 1e3), mhnUI.page.show()
}, function() {
$(".mhn-lock-title").html('<span>Opps! pattern is not correct</span>'), $(".patt-holder").removeClass("patt-success"), setTimeout(function() {
pattern.reset(), mhnUI.message()
}, 2e3)
})
},
message: function() {
$(".mhn-lock-title span").fadeOut(), setTimeout(function() {
$(".mhn-lock-title").html($(".mhn-lock-title").data("title")), $(".mhn-lock-title span").fadeIn()
}, 500)
},
datetime: function() {
var t = new Array("星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"),
e = new Array("一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"),
n = new Date,
i = n.getYear();
1e3 > i && (i += 1900);
var a = n.getDay(),
o = n.getMonth(),
s = n.getDate();
10 > s && (s = "0" + s);
var h = n.getHours(),
c = n.getMinutes(),
u = n.getSeconds(),
l = "AM";
h >= 12 && (l = "PM"), h > 12 && (h -= 12), 0 == h && (h = 12), 9 >= c && (c = "0" + c), 9 >= u && (u = "0" + u), $(".mhn-ui-date-time .mhn-ui-day").text(t[a]), $(".mhn-ui-date-time .mhn-ui-date").text(e[o] + " " + s + ", " + i), $(".mhn-ui-date-time .mhn-ui-time").text(h + ":" + c + " " + l), $(".mhn-ui-app-time").text(h + ":" + c + ":" + u + " " + l)
},
page: {
show: function(t) {
t = t ? t : "page-home", $(".mhn-ui-page").hide(), $(".mhn-ui-page." + t).show()
},
hide: function(t) {
t = t ? t : "page-lock", $(".mhn-ui-page").hide(), $(".mhn-ui-page." + t).show()
}
},
filter: function() {
$(".mhn-ui-filter .mhn-ui-btn").click(function() {
$(this).toggleClass("active"), $(".mhn-ui-filter-list").toggle(100)
}), $(".mhn-ui-filter-list>div").click(function() {
var t = $(this).data("filter");
$(this).siblings().removeAttr("class"), $(this).addClass("active");
var e = function(t) {
$(".mhn-ui-apps .mhn-ui-col").fadeOut(400), $('.mhn-ui-apps .mhn-ui-col[data-filter="' + t + '"]').fadeIn(400)
};
switch(t) {
case "all":
$(".mhn-ui-apps .mhn-ui-col").fadeIn(400);
break;
case "general":
e(t);
break;
case "social":
e(t);
break;
case "credits":
e(t)
}
$(".mhn-ui-filter-list").toggle(100), $(".mhn-ui-filter .mhn-ui-btn").removeClass("active"), $(".mhn-ui-page-title").text($(this).text())
})
},
colors: function() {
$(".mhn-ui-icon span").on("mouseover", function() {
$(this).css("background", $(this).data("color"))
}).on("mouseout", function() {
$(this).removeAttr("style")
})
},
links: {
setup: function() {
$(".mhn-ui-apps .mhn-ui-icon").click(function() {
var t = $(this).data("href"),
e = $(this).data("open");
t && mhnUI.links.href(t), e && mhnUI.page.show(e)
})
},
href: function(t) {
mhnUI.dialog.show(t)
}
},
dialog: {
setup: function() {
$('.mhn-ui-dialog-wrap,.mhn-ui-dialog-wrap a[data-action="cancel"]').click(function() {
mhnUI.dialog.hide()
}), $(".mhn-ui-dialog").click(function(t) {
t.stopPropagation()
}), $('.mhn-ui-dialog a[data-action="confirm"]').click(function() {
setTimeout(function() {
mhnUI.dialog.hide()
}, 400)
})
},
show: function(t) {
$('.mhn-ui-dialog-wrap a[data-action="confirm"]').attr("href", t), $(".mhn-ui-dialog-wrap").show()
},
hide: function() {
$('.mhn-ui-dialog-wrap a[data-action="confirm"]').removeAttr("href"), $(".mhn-ui-dialog-wrap").fadeOut(400)
}
}
};
/*
patternLock.js v 0.5.0
*/
! function(t, e, n, a) {
"use strict";
function r(t) {
for(var e = t.holder, n = t.option, a = n.matrix, r = n.margin, i = n.radius, o = ['<ul style="padding:' + r + 'px">'], s = 0, l = a[0] * a[1]; l > s; s++) o.push('<li style="margin:' + r + "px; width : " + 2 * i + "px; height : " + 2 * i + "px; -webkit-border-radius: " + i + "px; -moz-border-radius: " + i + "px; border-radius: " + i + 'px; "><div></div></li>');
o.push("</ul>"), e.html(o.join("")).css({
width: a[1] * (2 * i + 2 * r) + 2 * r + "px",
height: a[0] * (2 * i + 2 * r) + 2 * r + "px"
}), t.pattCircle = t.holder.find(".patt-circ")
}
function i(t, e, n, a) {
var r = e - t,
i = a - n;
return {
length: Math.ceil(Math.sqrt(r * r + i * i)),
angle: Math.round(180 * Math.atan2(i, r) / Math.PI)
}
}
function o() {}
function s(e, n) {
var a = this,
i = a.token = Math.random(),
h = p[i] = new o,
u = h.holder = t(e);
if(0 != u.length) {
h.object = a, n = h.option = t.extend({}, s.defaults, n), r(h), u.addClass("patt-holder"), "static" == u.css("position") && u.css("position", "relative"), u.on("touchstart mousedown", function(t) {
d.call(this, t, a)
}), h.option.onDraw = n.onDraw || l;
var c = n.mapper;
h.mapperFunc = "object" == typeof c ? function(t) {
return c[t]
} : "function" == typeof c ? c : l, h.option.mapper = null
}
}
var l = function() {},
p = {},
d = function(e, a) {
e.preventDefault();
var r = p[a.token];
if(!r.disabled) {
r.option.patternVisible || r.holder.addClass("patt-hidden");
var i = "touchstart" == e.type ? "touchmove" : "mousemove",
o = "touchstart" == e.type ? "touchend" : "mouseup";
t(this).on(i + ".pattern-move", function(t) {
h.call(this, t, a)
}), t(n).one(o, function() {
u.call(this, e, a)
});
var s = r.holder.find(".patt-wrap"),
l = s.offset();
r.wrapTop = l.top, r.wrapLeft = l.left, a.reset()
}
},
h = function(e, n) {
e.preventDefault();
var a = e.pageX || e.originalEvent.touches[0].pageX,
r = e.pageY || e.originalEvent.touches[0].pageY,
o = p[n.token],
s = o.pattCircle,
l = o.patternAry,
d = o.option.lineOnMove,
h = o.getIdxFromPoint(a, r),
u = h.idx,
c = o.mapperFunc(u) || u;
if(l.length > 0) {
var f = i(o.lineX1, h.x, o.lineY1, h.y);
o.line.css({
width: f.length + 10 + "px",
transform: "rotate(" + f.angle + "deg)"
})
}
if(u) {
if(-1 == l.indexOf(c)) {
var v, m = t(s[u - 1]);
if(o.lastPosObj) {
for(var g = o.lastPosObj, x = g.i, w = g.j, b = Math.abs(h.i - x), j = Math.abs(h.j - w);
(0 == b && j > 1 || 0 == j && b > 1 || j == b && j > 1) && (w != h.j || x != h.i);) {
x = b ? Math.min(h.i, x) + 1 : x, w = j ? Math.min(h.j, w) + 1 : w, b = Math.abs(h.i - x), j = Math.abs(h.j - w);
var M = (w - 1) * o.option.matrix[1] + x,
y = o.mapperFunc(M) || M; - 1 == l.indexOf(y) && (t(s[M - 1]).addClass("hovered"), l.push(y))
}
v = [], h.j - g.j > 0 ? v.push("s") : h.j - g.j < 0 ? v.push("n") : 0, h.i - g.i > 0 ? v.push("e") : h.i - g.i < 0 ? v.push("w") : 0, v = v.join("-")
}
m.addClass("hovered"), l.push(c);
var P = o.option.margin,
k = o.option.radius,
C = (h.i - 1) * (2 * P + 2 * k) + 2 * P + k,
O = (h.j - 1) * (2 * P + 2 * k) + 2 * P + k;
if(1 != l.length) {
var D = i(o.lineX1, C, o.lineY1, O);
o.line.css({
width: D.length + 10 + "px",
transform: "rotate(" + D.angle + "deg)"
}), d || o.line.show()
}
v && (o.lastElm.addClass(v + " dir"), o.line.addClass(v + " dir"));
var E = t('<div style="top:' + (O - 5) + "px; left:" + (C - 5) + 'px"></div>');
o.line = E, o.lineX1 = C, o.lineY1 = O, o.holder.append(E), d || o.line.hide(), o.lastElm = m
}
o.lastPosObj = h
}
},
u = function(t, e) {
t.preventDefault();
var n = p[e.token],
a = n.patternAry.join("");
n.holder.off(".pattern-move").removeClass("patt-hidden"), a && (n.option.onDraw(a), n.line.remove(), n.rightPattern && (a == n.rightPattern ? n.onSuccess() : (n.onError(), e.error